<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Ruby Biscuit]]></title><description><![CDATA[Chaque mois, on ouvre les coulisses d'une agence Rails spécialisée dans la finance depuis plus de 10 ans. Ruby Biscuit, c'est ce qu'on apprend en chemin, partagé par l'équipe Capsens, en français.
Des retours terrain. Du vrai code. Rien que ce qui compte.]]></description><link>https://www.rubybiscuit.fr</link><image><url>https://substackcdn.com/image/fetch/$s_!9H-9!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06f5ed06-7120-4671-826c-ea4f9c61fea0_834x834.png</url><title>Ruby Biscuit</title><link>https://www.rubybiscuit.fr</link></image><generator>Substack</generator><lastBuildDate>Thu, 30 Apr 2026 23:00:04 GMT</lastBuildDate><atom:link href="https://www.rubybiscuit.fr/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[L'équipe Capsens]]></copyright><language><![CDATA[fr]]></language><webMaster><![CDATA[rubybiscuit@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[rubybiscuit@substack.com]]></itunes:email><itunes:name><![CDATA[Mélanie]]></itunes:name></itunes:owner><itunes:author><![CDATA[Mélanie]]></itunes:author><googleplay:owner><![CDATA[rubybiscuit@substack.com]]></googleplay:owner><googleplay:email><![CDATA[rubybiscuit@substack.com]]></googleplay:email><googleplay:author><![CDATA[Mélanie]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[🍪 🔄 Synchronisez votre CRM depuis Rails avec Etlify]]></title><description><![CDATA[Ce mois-ci, nous parlons synchronisations CRM depuis Rails, temps de lecture : 8 minutes]]></description><link>https://www.rubybiscuit.fr/p/synchronisez-votre-crm-depuis-rails</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/synchronisez-votre-crm-depuis-rails</guid><pubDate>Wed, 01 Apr 2026 14:01:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/96080307-0597-4629-87c9-a2231034c90f_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#128680;Cette &#233;dition est relativement longue, votre bo&#238;te mail risque de la tronquer. Je vous conseille de cliquer sur le titre ci-dessus pour l&#8217;ouvrir dans votre navigateur web. &#128680;  </p><div><hr></div><p>Hello les petits Biscuits !</p><p>Bienvenue sur la <strong>44&#232;me &#233;dition de Ruby Biscuit</strong>.<br>Vous &#234;tes maintenant <strong>612 abonn&#233;s</strong> &#129395;</p><p><em>Vous lisez Ruby Biscuit, la newsletter Rails de Capsens. <a href="https://www.rubybiscuit.fr/subscribe">S&#8217;abonner gratuitement</a></em></p><div><hr></div><p><strong>Parenth&#232;se sur notre virage IA</strong></p><p>Chez Capsens, on n&#8217;&#233;crit plus vraiment de code nous-m&#234;mes. C&#8217;est Claude qui code, nous on fait de l&#8217;archi, on spec, on relit, on teste, on valide. Le r&#233;sultat : environ x1,5 de productivit&#233; sur les d&#233;veloppements qu&#8217;on r&#233;alise pour nos clients.<br>Ce n&#8217;est pas encore le mythique x3. On h&#233;berge des plateformes avec de gros flux financiers et des documents d&#8217;identit&#233;, la maintenabilit&#233; &#224; long terme et la s&#233;curit&#233; ne sont pas n&#233;gociables pour nous. La relecture humaine et les tests ont un co&#251;t et on le sait. On travaille activement pour gagner en productivit&#233; sur ces sujets afin qu&#8217;ils ne limitent pas les gains de productivit&#233;. Notre nouveau directeur technique est &#224; fond l&#224;-dessus.<br>On propose aujourd&#8217;hui cet accompagnement &#224; d&#8217;autres &#233;quipes tech. Si vous cherchez &#224; prendre ce virage avec une &#233;quipe qui l&#8217;a fait concr&#232;tement, on serait ravis d&#8217;en discuter.<br><br>Bonne lecture.</p><div><hr></div><p>Chez l&#8217;un de nos clients qui propose une plateforme d&#8217;investissements en ligne, l&#8217;&#233;quipe commerciale travaille depuis Airtable. C&#8217;est leur outil au quotidien pour suivre les utilisateurs, les investissements et les mouvements financiers. Sauf que la donn&#233;e source, elle, vit dans l&#8217;app Rails. Il faut donc pousser les donn&#233;es de Rails vers Airtable et les garder &#224; jour.</p><p>Au d&#233;but, nous synchronisions un model. Puis deux. Puis cinq. Chaque model avait son propre worker, son serializer, sa transaction, ses specs. Six &#224; huit fichiers &#224; cr&#233;er pour chaque nouveau model synchronis&#233;. Vingt-quatre fichiers d&#233;di&#233;s rien qu&#8217;&#224; la sync. Et quand &#231;a cassait, la synchronisation p&#233;riodique repassait sans corriger le probl&#232;me et les erreurs polluaient Appsignal, notre outil de monitoring.</p><p>Le vrai probl&#232;me n&#8217;&#233;tait pas le code existant. C&#8217;&#233;tait la question : &#8220;combien de temps pour ajouter un sixi&#232;me model ?&#8221; R&#233;ponse : une demi-journ&#233;e, &#224; c&#226;bler les callbacks, copier-coller la logique de digest, prier pour ne rien oublier.</p><p></p><p>Nous avons d&#8217;abord cherch&#233; des gems existantes. La plupart ciblaient un CRM sp&#233;cifique (HubSpot, Salesforce) ou imposaient un couplage fort avec ActiveRecord. Rien qui colle &#224; notre besoin : un syst&#232;me d&#233;claratif, agnostique du CRM, capable de g&#233;rer les d&#233;pendances entre models.</p><p>Nous avons d&#233;cid&#233; d&#8217;&#233;crire notre propre gem. L&#8217;id&#233;e de d&#233;part : rendre la sync CRM aussi simple qu&#8217;un <code>has_many</code> ou un <code>validates</code>. D&#233;clarer dans le model ce que nous synchronisons, comment nous le transformons, et laisser la gem g&#233;rer le reste.</p><p>Quelques mois plus tard, nous avons <strong><a href="https://github.com/CapSens/etlify">Etlify</a></strong>, une gem Rails open source cr&#233;&#233;e par Capsens. Le nom vient de l&#8217;acronyme ETL : <strong>E</strong>xtract, <strong>T</strong>ransform, <strong>L</strong>oad. C&#8217;est exactement ce que fait la gem : extraire les donn&#233;es d&#8217;ActiveRecord, les transformer via un serializer, et les charger dans le CRM.</p><p></p><h2>Ce que nous y avons gagn&#233;</h2><p>Avant d&#8217;entrer dans le code, voici les chiffres mesur&#233;s sur notre migration :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KF_K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KF_K!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png 424w, https://substackcdn.com/image/fetch/$s_!KF_K!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png 848w, https://substackcdn.com/image/fetch/$s_!KF_K!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png 1272w, https://substackcdn.com/image/fetch/$s_!KF_K!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KF_K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png" width="1396" height="716" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:716,&quot;width&quot;:1396,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:166222,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KF_K!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png 424w, https://substackcdn.com/image/fetch/$s_!KF_K!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png 848w, https://substackcdn.com/image/fetch/$s_!KF_K!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png 1272w, https://substackcdn.com/image/fetch/$s_!KF_K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8cb9968-6582-4c90-8afe-fdda6dbed40e_1396x716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>L&#8217;architecture en 30 secondes</h2><p>Etlify repose sur quatre briques, qui suivent la logique ETL.</p><p><strong>Extract : la d&#233;tection des stale records</strong> scanne p&#233;riodiquement vos models pour d&#233;tecter les records dont le digest a chang&#233; sans appel explicite, et relance leur synchronisation.</p><p><strong>Transform : le serializer</strong> (appel&#233; <em>dictionary</em> dans la gem) transforme un record ActiveRecord en Hash CRM-compatible. Un par model synchronis&#233;.</p><p><strong>Load : le synchronizer</strong> orchestre le chargement. Il calcule une empreinte SHA256 du payload. Si rien n&#8217;a chang&#233;, il passe. Sinon il appelle <strong>l&#8217;adapter</strong> (la couche HTTP vers le CRM) et stocke le r&#233;sultat dans <code>crm_synchronisations</code>.</p><pre><code><code>crm_sync! &#8594; Worker (async) &#8594; Synchronizer</code>
<code>  &#9500;&#9472;&#9472; sync_if &#8594; false ?        &#8594; :skipped</code>
<code>  &#9500;&#9472;&#9472; dependency manquante ?   &#8594; PendingSync &#8594; :buffered</code>
<code>  &#9500;&#9472;&#9472; digest identique ?       &#8594; :not_modified</code>
<code>  &#9492;&#9472;&#9472; Serializer#to_h &#8594; Adapter#upsert! &#8594; :synced</code></code></pre><p>Le worker, l&#8217;adapter et le synchronizer sont partag&#233;s entre tous les models. Vous n&#8217;&#233;crivez que ce qui est sp&#233;cifique : le serializer et la config.</p><h2>Impl&#233;mentation</h2><h3>Installation</h3><p>Ajoutez la gem &#224; votre Gemfile, avec <code>faraday</code> (HTTP), <code>sidekiq-throttled</code> (rate limiting) et <code>sidekiq-unique-jobs</code> (d&#233;dup des jobs) si ce n&#8217;est pas d&#233;j&#224; fait :</p><pre><code><code>gem "etlify", git: "git@github.com:CapSens/etlify.git", tag: "v0.9.3"</code></code></pre><p>Puis :</p><pre><code><code>bundle install</code></code></pre><p>Avant de lancer les migrations, cr&#233;ez l&#8217;initializer pour que la gem soit configur&#233;e :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!juUN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!juUN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png 424w, https://substackcdn.com/image/fetch/$s_!juUN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png 848w, https://substackcdn.com/image/fetch/$s_!juUN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png 1272w, https://substackcdn.com/image/fetch/$s_!juUN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!juUN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png" width="1324" height="726" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:726,&quot;width&quot;:1324,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:122896,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!juUN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png 424w, https://substackcdn.com/image/fetch/$s_!juUN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png 848w, https://substackcdn.com/image/fetch/$s_!juUN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png 1272w, https://substackcdn.com/image/fetch/$s_!juUN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6523edb-3c14-4e6a-bf89-d410084d993b_1324x726.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ensuite, g&#233;n&#233;rez et lancez les migrations :</p><pre><code><code>rails g etlify:migration create_crm_synchronisations</code>
<code>rails g etlify:migration create_etlify_pending_syncs</code>
<code>rails db:migrate</code></code></pre><p><code>crm_synchronisations</code> stocke pour chaque record synchronis&#233; :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tqt8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tqt8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png 424w, https://substackcdn.com/image/fetch/$s_!tqt8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png 848w, https://substackcdn.com/image/fetch/$s_!tqt8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png 1272w, https://substackcdn.com/image/fetch/$s_!tqt8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tqt8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png" width="1380" height="568" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/adb29e44-d860-467c-97fa-01349de37308_1380x568.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:568,&quot;width&quot;:1380,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:108890,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tqt8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png 424w, https://substackcdn.com/image/fetch/$s_!tqt8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png 848w, https://substackcdn.com/image/fetch/$s_!tqt8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png 1272w, https://substackcdn.com/image/fetch/$s_!tqt8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb29e44-d860-467c-97fa-01349de37308_1380x568.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><code>etlify_pending_syncs</code> garde les syncs bloqu&#233;es par une d&#233;pendance (nous y reviendrons).</p><h3>L&#8217;adapter</h3><p>La gem fournit le contrat. L&#8217;adapter, c&#8217;est vous qui l&#8217;&#233;crivez. Voici le n&#244;tre pour Airtable :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5GAI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5GAI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png 424w, https://substackcdn.com/image/fetch/$s_!5GAI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png 848w, https://substackcdn.com/image/fetch/$s_!5GAI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png 1272w, https://substackcdn.com/image/fetch/$s_!5GAI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5GAI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png" width="1456" height="4309" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4309,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:744441,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5GAI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png 424w, https://substackcdn.com/image/fetch/$s_!5GAI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png 848w, https://substackcdn.com/image/fetch/$s_!5GAI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png 1272w, https://substackcdn.com/image/fetch/$s_!5GAI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d248856-b8fa-4da7-bc2a-16c2d5b1b214_1478x4374.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><code>upsert!</code> retourne le <code>crm_id</code>. <code>delete!</code> retourne un bool&#233;en. Les m&#233;thodes CRUD priv&#233;es sont du Faraday classique (POST pour cr&#233;er, PATCH pour mettre &#224; jour, GET avec <code>filterByFormula</code> pour chercher un record existant).</p><p>Point important : la gem ne d&#233;clenche <strong>pas</strong> <code>delete!</code> sur <code>after_destroy</code>. Si vous supprimez un record en base, le record c&#244;t&#233; CRM reste. &#192; vous de d&#233;cider o&#249; et quand appeler <code>delete!</code> explicitement.</p><p>Pour un autre CRM, impl&#233;mentez <code>upsert!</code>, <code>delete!</code> et le mapping d&#8217;erreurs dans <code>handle_response</code>. Le reste change, la m&#233;canique reste la m&#234;me.</p><h3>La config YAML</h3><p>Chaque model a son fichier YAML. L&#8217;id&#233;e c&#8217;est de d&#233;coupler vos noms de champs des IDs Airtable. Un champ renomm&#233; c&#244;t&#233; CRM ? Vous changez le YAML, pas le code.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!StAk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!StAk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png 424w, https://substackcdn.com/image/fetch/$s_!StAk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png 848w, https://substackcdn.com/image/fetch/$s_!StAk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png 1272w, https://substackcdn.com/image/fetch/$s_!StAk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!StAk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png" width="996" height="774" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:774,&quot;width&quot;:996,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:113575,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!StAk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png 424w, https://substackcdn.com/image/fetch/$s_!StAk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png 848w, https://substackcdn.com/image/fetch/$s_!StAk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png 1272w, https://substackcdn.com/image/fetch/$s_!StAk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F062f9027-6cf4-4a03-a5a6-8ba75c7cdb55_996x774.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>D&#233;clarer un model synchronisable</h3><p>Deux choses &#224; ajouter au model : <code>include Etlify::Model</code> pour le DSL, et <code>has_many :crm_synchronisations</code> pour l&#8217;association polymorphique.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LWw6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LWw6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png 424w, https://substackcdn.com/image/fetch/$s_!LWw6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png 848w, https://substackcdn.com/image/fetch/$s_!LWw6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png 1272w, https://substackcdn.com/image/fetch/$s_!LWw6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LWw6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png" width="1168" height="1302" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1302,&quot;width&quot;:1168,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:202294,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LWw6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png 424w, https://substackcdn.com/image/fetch/$s_!LWw6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png 848w, https://substackcdn.com/image/fetch/$s_!LWw6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png 1272w, https://substackcdn.com/image/fetch/$s_!LWw6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29513999-2160-47aa-9642-e2e58a33e063_1168x1302.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Le YAML est charg&#233; au boot via la constante <code>CONFIG_PATH</code> du serializer, un seul endroit o&#249; le chemin est d&#233;fini. <code>crm_object_type</code> re&#231;oit le table ID. <code>id_property</code> sert &#224; retrouver un record existant c&#244;t&#233; CRM.</p><p>Quatre options dans le DSL m&#233;ritent que nous nous y arr&#234;tions. Nous avons mis un moment &#224; bien les cerner.</p><p><code>sync_dependencies: [:customer]</code> est bloquant. Si le Customer n&#8217;a pas de <code>crm_id</code>, la sync est mise en attente. Un <code>PendingSync</code> est cr&#233;&#233;. Etlify d&#233;clenche la sync du Customer en cascade. Quand celui-ci sera sync, les syncs en attente seront ex&#233;cut&#233;es.</p><p><code>dependencies: [:products]</code> est non bloquant. Le serializer de l&#8217;Order inclut des donn&#233;es des Products (ex : leur nom, leur prix). Si un Product change, le digest SHA256 de l&#8217;Order change aussi au prochain calcul. Le cron de stale records d&#233;tecte cette diff&#233;rence et re-sync l&#8217;Order automatiquement.</p><p><code>sync_if</code> filtre les records &#233;ligibles. Si le lambda retourne <code>false</code>, le synchronizer retourne <code>:skipped</code> et ne touche pas au CRM. Attention : un record d&#233;j&#224; synchronis&#233; dont le <code>sync_if</code> retourne ensuite <code>false</code> ne sera ni re-sync ni supprim&#233; c&#244;t&#233; CRM. Cela signifie que si un record change d&#8217;&#233;tat (par exemple, il redevient non &#233;ligible), il restera tel quel c&#244;t&#233; CRM. Pour le retirer, appelez <code>delete!</code> explicitement. &#192; utiliser avec discernement, sur des &#233;tats r&#233;ellement d&#233;finitifs.</p><p><code>stale_scope</code> restreint le scan du cron aux records concern&#233;s.</p><p>La cascade fonctionne en profondeur. Imaginez : Order &#8594; Customer &#8594; Company. Etlify met en attente et remonte la cha&#238;ne jusqu&#8217;&#224; trouver un <code>crm_id</code>. Si &#231;a vous rappelle les poup&#233;es russes, c&#8217;est normal.</p><h3>Le serializer</h3><p>Le serializer, c&#8217;est le fichier o&#249; vous d&#233;cidez ce que le CRM voit de vos donn&#233;es. D&#8217;abord, la classe de base :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E2V2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E2V2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png 424w, https://substackcdn.com/image/fetch/$s_!E2V2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png 848w, https://substackcdn.com/image/fetch/$s_!E2V2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png 1272w, https://substackcdn.com/image/fetch/$s_!E2V2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E2V2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png" width="1150" height="1446" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1446,&quot;width&quot;:1150,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:179500,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!E2V2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png 424w, https://substackcdn.com/image/fetch/$s_!E2V2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png 848w, https://substackcdn.com/image/fetch/$s_!E2V2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png 1272w, https://substackcdn.com/image/fetch/$s_!E2V2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5760f880-0229-40d2-bd61-3086b10987ae_1150x1446.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Le <code>helpers</code>/<code>h</code> donne acc&#232;s aux helpers Rails (<code>h.number_to_currency</code>, <code>h.truncate</code>, etc.) directement dans vos serializers. &#199;a d&#233;panne plus souvent qu&#8217;on ne le pense.</p><p>Puis le serializer du model :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QSo2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QSo2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png 424w, https://substackcdn.com/image/fetch/$s_!QSo2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png 848w, https://substackcdn.com/image/fetch/$s_!QSo2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png 1272w, https://substackcdn.com/image/fetch/$s_!QSo2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QSo2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png" width="1150" height="1302" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1302,&quot;width&quot;:1150,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:163680,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QSo2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png 424w, https://substackcdn.com/image/fetch/$s_!QSo2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png 848w, https://substackcdn.com/image/fetch/$s_!QSo2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png 1272w, https://substackcdn.com/image/fetch/$s_!QSo2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4feb5f-25d5-4488-a6ba-d4924a32dc35_1150x1302.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Les cl&#233;s du Hash retourn&#233; par <code>to_h</code> sont les field IDs Airtable, pas vos noms de colonnes. Un serializer par model synchronis&#233;.</p><h3>Le worker</h3><p>Le model sait quoi, le serializer sait comment. Reste le transport. La gem fournit un <code>Etlify::SyncJob</code> par d&#233;faut. Ici nous le rempla&#231;ons par un worker Sidekiq avec throttling pour respecter les limites Airtable :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ucuI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ucuI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png 424w, https://substackcdn.com/image/fetch/$s_!ucuI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png 848w, https://substackcdn.com/image/fetch/$s_!ucuI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png 1272w, https://substackcdn.com/image/fetch/$s_!ucuI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ucuI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png" width="1188" height="2262" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2262,&quot;width&quot;:1188,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:349001,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ucuI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png 424w, https://substackcdn.com/image/fetch/$s_!ucuI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png 848w, https://substackcdn.com/image/fetch/$s_!ucuI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png 1272w, https://substackcdn.com/image/fetch/$s_!ucuI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bb06e05-8991-4472-b4a6-d495d6b7be2c_1188x2262.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Le throttle <code>:airtable_api</code> doit &#234;tre d&#233;clar&#233; dans votre initializer Sidekiq :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aopZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aopZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png 424w, https://substackcdn.com/image/fetch/$s_!aopZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png 848w, https://substackcdn.com/image/fetch/$s_!aopZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png 1272w, https://substackcdn.com/image/fetch/$s_!aopZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aopZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png" width="960" height="438" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:438,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55455,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aopZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png 424w, https://substackcdn.com/image/fetch/$s_!aopZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png 848w, https://substackcdn.com/image/fetch/$s_!aopZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png 1272w, https://substackcdn.com/image/fetch/$s_!aopZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fe4269-0947-4d5c-92e2-65f3151c85f2_960x438.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Et la queue <code>crm</code> dans votre config :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2q9Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2q9Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png 424w, https://substackcdn.com/image/fetch/$s_!2q9Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png 848w, https://substackcdn.com/image/fetch/$s_!2q9Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png 1272w, https://substackcdn.com/image/fetch/$s_!2q9Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2q9Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png" width="960" height="294" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:294,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:20068,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2q9Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png 424w, https://substackcdn.com/image/fetch/$s_!2q9Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png 848w, https://substackcdn.com/image/fetch/$s_!2q9Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png 1272w, https://substackcdn.com/image/fetch/$s_!2q9Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f09cbeb-4dcd-4339-8550-8776d989c98a_960x294.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Le worker a <code>retry: false</code> c&#244;t&#233; Sidekiq. Le synchronizer fait un seul essai par invocation : s&#8217;il &#233;choue, il incr&#233;mente <code>error_count</code> dans <code>crm_synchronisations</code> et passe au suivant. Pas de retry en boucle, c&#8217;est le cron des stale records qui rattrape le coup au cycle suivant. Apr&#232;s 3 &#233;checs cons&#233;cutifs (configurable via <code>max_sync_errors</code>), le record est exclu du cron automatique. Il reste synchronisable manuellement via <code>crm_sync!</code>. Un appel r&#233;ussi remet <code>error_count</code> &#224; z&#233;ro.</p><p>En compl&#233;ment, un worker cron rattrape les records dont le digest a chang&#233; sans appel explicite :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bAk_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bAk_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png 424w, https://substackcdn.com/image/fetch/$s_!bAk_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png 848w, https://substackcdn.com/image/fetch/$s_!bAk_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png 1272w, https://substackcdn.com/image/fetch/$s_!bAk_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bAk_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png" width="1208" height="1062" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1062,&quot;width&quot;:1208,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:137130,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bAk_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png 424w, https://substackcdn.com/image/fetch/$s_!bAk_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png 848w, https://substackcdn.com/image/fetch/$s_!bAk_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png 1272w, https://substackcdn.com/image/fetch/$s_!bAk_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c8b3313-89d4-4115-9115-031608e9c27c_1208x1062.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Planifiez-le dans votre <code>config/schedule.yml</code> &#224; la fr&#233;quence qui vous convient.</p><h3>D&#233;clencher la sync</h3><p>Tout est en place. Pour synchroniser un record : <code>order.crm_sync!(crm_name: :airtable)</code>. Un one-liner depuis vos services, transactions ou controllers. Nous l&#8217;appelons apr&#232;s un paiement valid&#233;, dans nos transactions de membership, dans les services d&#8217;onboarding. Le synchronizer g&#232;re le reste.</p><h3>Migrer depuis un syst&#232;me legacy</h3><p>Si vos models avaient une colonne <code>airtable_id</code>, ajoutez un fallback :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ADEz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ADEz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png 424w, https://substackcdn.com/image/fetch/$s_!ADEz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png 848w, https://substackcdn.com/image/fetch/$s_!ADEz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png 1272w, https://substackcdn.com/image/fetch/$s_!ADEz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ADEz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png" width="960" height="438" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:438,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:52679,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ADEz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png 424w, https://substackcdn.com/image/fetch/$s_!ADEz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png 848w, https://substackcdn.com/image/fetch/$s_!ADEz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png 1272w, https://substackcdn.com/image/fetch/$s_!ADEz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bef7c15-c4cd-4199-a381-3d31ff5d476c_960x438.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Les deux syst&#232;mes cohabitent. Vous migrez model par model.</p><p>Pour &#233;viter de re-sync tous vos records via l&#8217;API, un rake task cr&#233;e les <code>CrmSynchronisation</code> en masse &#224; partir des <code>airtable_id</code> existants :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!90nI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!90nI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png 424w, https://substackcdn.com/image/fetch/$s_!90nI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png 848w, https://substackcdn.com/image/fetch/$s_!90nI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png 1272w, https://substackcdn.com/image/fetch/$s_!90nI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!90nI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png" width="1130" height="1494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1494,&quot;width&quot;:1130,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:228132,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!90nI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png 424w, https://substackcdn.com/image/fetch/$s_!90nI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png 848w, https://substackcdn.com/image/fetch/$s_!90nI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png 1272w, https://substackcdn.com/image/fetch/$s_!90nI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F013ffcee-3bec-4e27-9cb3-c1973e48eecc_1130x1494.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Le task stocke l&#8217;empreinte actuelle. Seuls les records modifi&#233;s apr&#232;s le backfill seront re-sync. Pas de flood API.</p><h2>Tests</h2><p>Pour vos tests, mockez les appels HTTP vers Airtable plut&#244;t que de remplacer l&#8217;adapter. Cela permet de tester le vrai comportement de bout en bout, y compris votre <code>handle_response</code> et le mapping d&#8217;erreurs :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!InTG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!InTG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png 424w, https://substackcdn.com/image/fetch/$s_!InTG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png 848w, https://substackcdn.com/image/fetch/$s_!InTG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png 1272w, https://substackcdn.com/image/fetch/$s_!InTG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!InTG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png" width="1150" height="678" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:678,&quot;width&quot;:1150,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:98291,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!InTG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png 424w, https://substackcdn.com/image/fetch/$s_!InTG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png 848w, https://substackcdn.com/image/fetch/$s_!InTG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png 1272w, https://substackcdn.com/image/fetch/$s_!InTG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82151230-cd19-4810-92c9-94167bfeea6b_1150x678.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Puis testez vos serializers et le comportement de sync :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qKGu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qKGu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png 424w, https://substackcdn.com/image/fetch/$s_!qKGu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png 848w, https://substackcdn.com/image/fetch/$s_!qKGu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png 1272w, https://substackcdn.com/image/fetch/$s_!qKGu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qKGu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png" width="1246" height="2454" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2454,&quot;width&quot;:1246,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:382635,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/192838426?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qKGu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png 424w, https://substackcdn.com/image/fetch/$s_!qKGu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png 848w, https://substackcdn.com/image/fetch/$s_!qKGu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png 1272w, https://substackcdn.com/image/fetch/$s_!qKGu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc953f5dd-eccd-4464-9a0a-52787f423e86_1246x2454.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Etlify n&#8217;est pas limit&#233;e &#224; Airtable. L&#8217;architecture par adapter permet de brancher n&#8217;importe quel CRM. La gem embarque un <code>NullAdapter</code> pour vos tests et le d&#233;veloppement local. Si vous utilisez ActiveAdmin, les tables <code>crm_synchronisations</code> et <code>etlify_pending_syncs</code> se branchent tr&#232;s bien pour monitorer vos syncs et relancer les records en erreur.</p><p>Si vous synchronisez un CRM depuis Rails et que &#231;a commence &#224; devenir p&#233;nible, essayez-la. Nous aurions aim&#233; l&#8217;avoir plus t&#244;t.</p><p>La gem est open source : <a href="http://github.com/CapSens/etlify">github.com/CapSens/etlify</a></p><p>&#8212; <em>Benjamin, d&#233;veloppeur chez Capsens</em></p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/p/synchronisez-votre-crm-depuis-rails?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Partager&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Si cet article vous a &#233;t&#233; utile, partagez-le &#224; un(e) coll&#232;gue.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/p/synchronisez-votre-crm-depuis-rails?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Partager&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/p/synchronisez-votre-crm-depuis-rails?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Partager</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[🍪🆔 Pourquoi et comment on est passé aux UUID v7 dans nos apps Rails ]]></title><description><![CDATA[Temps de lecture : 5 minutes]]></description><link>https://www.rubybiscuit.fr/p/pourquoi-et-comment-on-est-passe</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/pourquoi-et-comment-on-est-passe</guid><pubDate>Wed, 04 Mar 2026 15:03:10 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/27b8af35-eaff-4624-98c2-9cb8b85359df_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la <strong>43&#232;me &#233;dition de Ruby Biscuit</strong>.<br>Vous &#234;tes maintenant <strong>608 abonn&#233;s</strong> &#129395;</p><p>Bonne lecture.</p><p></p><p><em>Vous lisez Ruby Biscuit, la newsletter Rails de Capsens. <a href="https://www.rubybiscuit.fr/subscribe">S'abonner gratuitement</a></em></p><div><hr></div><p>Consid&#233;rez le contr&#244;leur suivant :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AXVq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AXVq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png 424w, https://substackcdn.com/image/fetch/$s_!AXVq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png 848w, https://substackcdn.com/image/fetch/$s_!AXVq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png 1272w, https://substackcdn.com/image/fetch/$s_!AXVq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AXVq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png" width="960" height="384" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ab15947a-9370-45b8-ad6a-d03954936473_960x384.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:384,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56964,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AXVq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png 424w, https://substackcdn.com/image/fetch/$s_!AXVq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png 848w, https://substackcdn.com/image/fetch/$s_!AXVq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png 1272w, https://substackcdn.com/image/fetch/$s_!AXVq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab15947a-9370-45b8-ad6a-d03954936473_960x384.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Il pr&#233;sente une faille de s&#233;curit&#233; &#233;vidente : tout utilisateur de la plateforme peut trouver des messages priv&#233;s qui ne lui sont pas destin&#233;s.</p><p>Consid&#233;rez &#233;galement cette URL :</p><pre><code><code>/investments/38</code></code></pre><p>Il expose une information sensible : un aper&#231;u du nombre d&#8217;investissements, surtout si<br>l&#8217;utilisateur vient de cr&#233;er le sien et a &#233;t&#233; dirig&#233; vers <em>InvestmentsController#show</em>.</p><p>En ce qui concerne le premier exemple, je doute que vous &#233;criviez du code cr&#233;ant une<br>telle faille de s&#233;curit&#233;. Vous avez pris le r&#233;flexe d&#8217;&#233;crire :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kBQ_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kBQ_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png 424w, https://substackcdn.com/image/fetch/$s_!kBQ_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png 848w, https://substackcdn.com/image/fetch/$s_!kBQ_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png 1272w, https://substackcdn.com/image/fetch/$s_!kBQ_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kBQ_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png" width="1186" height="384" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:384,&quot;width&quot;:1186,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60447,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kBQ_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png 424w, https://substackcdn.com/image/fetch/$s_!kBQ_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png 848w, https://substackcdn.com/image/fetch/$s_!kBQ_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png 1272w, https://substackcdn.com/image/fetch/$s_!kBQ_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b8c2f27-daba-4889-9f19-20f88d492748_1186x384.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Nous ne ma&#238;trisons cependant pas tout le <em>legacy code</em> d&#8217;une application, et ce genre de faille peut exister &#224; notre insu et de fa&#231;on moins &#233;vidente que dans l&#8217;exemple.</p><p>Quant au second exemple, il n&#8217;existe pas de modification facile &#224; appliquer au contr&#244;leur pour ne pas exposer l&#8217;identifiant s&#233;quentiel de l&#8217;investissement.</p><p>Les UUID peuvent r&#233;soudre ces deux probl&#232;mes en m&#234;me temps. Voyons comment !</p><div><hr></div><h1>Que sont les UUID ?</h1><p>UUID signifie <em>universally unique identifiers</em>. Les identifiants s&#233;quentiels traditionnels se<br>r&#233;p&#232;tent d&#8217;une table de la base de donn&#233;es &#224; une autre, ainsi que d&#8217;une application &#224; une autre. Il y a un User avec l&#8217;identifiant 1 et un PrivateMessage avec l&#8217;identifiant 1. Les m&#234;mes chiffres entiers qui augmentent un &#224; un sont utilis&#233;s dans beaucoup d&#8217;applications. Au contraire, les UUID sont des identifiants uniques, non seulement d&#8217;une table de base de donn&#233;es &#224; une autre, mais d&#8217;une application &#224; une autre. Chacun repr&#233;sente un <em>string</em> enti&#232;rement unique, qui ne sera jamais r&#233;p&#233;t&#233;.</p><p>Il y a plusieurs standards d&#8217;UUID, mais concentrons-nous sur celui &#233;tabli par l&#8217;Internet<br>Engineering Task Force. Ce standard comporte neuf versions, qui utilisent plusieurs strat&#233;gies pour produire un <em>string</em> hexad&#233;cimal de 32 caract&#232;res universellement unique.</p><p>Quelles sont ces strat&#233;gies ?</p><p>I. Utiliser des combinaisons de <em>namespaces hash&#233;s</em> et uniques<br>II. Utiliser des <em>timestamps</em> allant parfois jusqu&#8217;aux 100 nanosecondes<br>III. Utiliser des caract&#232;res al&#233;atoires<br>IV. Utiliser une combinaison de <em>timestamps</em> et de caract&#232;res al&#233;atoires</p><p>En voici un exemple :</p><pre><code><code>019bff1a-a80f-7bad-b4da-ea545f04972c</code></code></pre><p>qui met en &#339;uvre cette derni&#232;re strat&#233;gie.</p><p>Des neuf versions d&#8217;UUID, PostgreSQL n&#8217;en utilise que deux : les versions 4 et 7. La<br>version 4 des UUID est un <em>string</em> al&#233;atoire disponible depuis la version 13 de PostgreSQL. La version 7 des UUID, qui combine un <em>timestamp</em> et des caract&#232;res al&#233;atoires est disponible depuis la version 18 de PostgreSQL.</p><p>Le <em>timestamp</em> de la version 7 permet d&#8217;ordonner les objets par leur UUID, ce qui n&#8217;est<br>pas possible avec le <em>string</em> al&#233;atoire de la version 4. C&#8217;est pour cette raison que la version 7 est pr&#233;f&#233;rable &#224; la version 4 pour nos usages.</p><div><hr></div><h1>L&#8217;anatomie d&#8217;UUID version 7</h1><p>Int&#233;ressons-nous de plus pr&#232;s &#224; la version 7 des UUID. Notre UUID d&#8217;exemple comporte 32 caract&#232;res hexad&#233;cimaux, hors tirets :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!svYv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!svYv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png 424w, https://substackcdn.com/image/fetch/$s_!svYv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png 848w, https://substackcdn.com/image/fetch/$s_!svYv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png 1272w, https://substackcdn.com/image/fetch/$s_!svYv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!svYv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png" width="728" height="391" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72819f18-152e-4791-960c-de488e43d664_1474x792.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:782,&quot;width&quot;:1456,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:131298,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!svYv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png 424w, https://substackcdn.com/image/fetch/$s_!svYv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png 848w, https://substackcdn.com/image/fetch/$s_!svYv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png 1272w, https://substackcdn.com/image/fetch/$s_!svYv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72819f18-152e-4791-960c-de488e43d664_1474x792.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x6OH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x6OH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png 424w, https://substackcdn.com/image/fetch/$s_!x6OH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png 848w, https://substackcdn.com/image/fetch/$s_!x6OH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png 1272w, https://substackcdn.com/image/fetch/$s_!x6OH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x6OH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png" width="960" height="268" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:268,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27934,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!x6OH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png 424w, https://substackcdn.com/image/fetch/$s_!x6OH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png 848w, https://substackcdn.com/image/fetch/$s_!x6OH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png 1272w, https://substackcdn.com/image/fetch/$s_!x6OH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912f3775-59bc-4b38-95bb-f767db53f33a_960x268.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>to_i(16)</em> convertit l&#8217;hexad&#233;cimal en nombre de millisecondes, que nous convertissons ensuite en secondes.</p><p>Apr&#232;s le <em>timestamp</em>, il y a un 7, qui est invariable et repr&#233;sente la version d&#8217;UUID. S&#8217;il<br>s&#8217;agissait d&#8217;un UUID version 4, il y aurait un 4.</p><p>Les autres valeurs sont al&#233;atoires, &#224; l&#8217;exception du V, qui repr&#233;sente le variant de l&#8217;UUID.<br>Ce variant permet de distinguer le standard des UUID de l&#8217;Internet Engineering Task Force des autres standards. L&#8217;Internet Engineering Task Force utilise quatre valeurs pour repr&#233;senter ce variant : 8, 9, A et B, puisque ces valeurs, exprim&#233;es en binaire, commencent toujours par 10.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!epOY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!epOY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png 424w, https://substackcdn.com/image/fetch/$s_!epOY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png 848w, https://substackcdn.com/image/fetch/$s_!epOY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png 1272w, https://substackcdn.com/image/fetch/$s_!epOY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!epOY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png" width="1394" height="402" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:402,&quot;width&quot;:1394,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37121,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!epOY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png 424w, https://substackcdn.com/image/fetch/$s_!epOY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png 848w, https://substackcdn.com/image/fetch/$s_!epOY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png 1272w, https://substackcdn.com/image/fetch/$s_!epOY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F850b9858-10f0-40c6-ad0c-278a0f085b37_1394x402.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Dans notre exemple, il s&#8217;agit d&#8217;un B :</p><pre><code><code>019bff1a-a80f-7bad-B4da-ea545f04972c</code></code></pre><p>Cela nous laisse 18 caract&#232;res hexad&#233;cimaux al&#233;atoires. Sans m&#234;me prendre en compte le <em>timestamp</em>, la probabilit&#233; de trouver ces 18 caract&#232;res et le variant est 16 caract&#232;res hexad&#233;cimaux puissance 18 fois 4 variants.</p><p>Ce nombre de possibilit&#233;s colossal rend l&#8217;UUID unique, impossible &#224; deviner et donc<br>s&#233;curis&#233;.</p><div><hr></div><h1>Comment utiliser les UUID avec une base de donn&#233;es PostgreSQL ?</h1><p>Maintenant que vous avez une compr&#233;hension th&#233;orique des UUID, regardons comment les utiliser dans une application Ruby on Rails avec PostgreSQL.</p><p>Les UUID en PostgreSQL sont des types de colonnes uuid, tout comme les identifiants<br>s&#233;quentiels ont le type bigint.</p><p>M&#234;me si nous avons une pr&#233;f&#233;rence pour le version 7 des UUID, regardons l&#8217;impl&#233;mentation de ces deux versions.</p><p>Pour cr&#233;er une nouvelle table avec un UUID v4, il suffit de faire :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4_EK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4_EK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png 424w, https://substackcdn.com/image/fetch/$s_!4_EK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png 848w, https://substackcdn.com/image/fetch/$s_!4_EK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png 1272w, https://substackcdn.com/image/fetch/$s_!4_EK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4_EK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png" width="1248" height="190" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:1248,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:21375,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4_EK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png 424w, https://substackcdn.com/image/fetch/$s_!4_EK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png 848w, https://substackcdn.com/image/fetch/$s_!4_EK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png 1272w, https://substackcdn.com/image/fetch/$s_!4_EK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1fdb35c-2a93-4ad7-8380-0d24557b7988_1248x190.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Avec un UUID v7</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LQPS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LQPS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png 424w, https://substackcdn.com/image/fetch/$s_!LQPS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png 848w, https://substackcdn.com/image/fetch/$s_!LQPS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png 1272w, https://substackcdn.com/image/fetch/$s_!LQPS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LQPS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png" width="1264" height="190" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:1264,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22870,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LQPS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png 424w, https://substackcdn.com/image/fetch/$s_!LQPS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png 848w, https://substackcdn.com/image/fetch/$s_!LQPS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png 1272w, https://substackcdn.com/image/fetch/$s_!LQPS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F701a135f-9153-4f6d-be6b-8b28d317a412_1264x190.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Les clefs &#233;trang&#232;res de l&#8217;une et de l&#8217;autre version doivent pr&#233;ciser le type uuid :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Dw5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Dw5w!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png 424w, https://substackcdn.com/image/fetch/$s_!Dw5w!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png 848w, https://substackcdn.com/image/fetch/$s_!Dw5w!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png 1272w, https://substackcdn.com/image/fetch/$s_!Dw5w!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Dw5w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png" width="1374" height="190" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:1374,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26809,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Dw5w!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png 424w, https://substackcdn.com/image/fetch/$s_!Dw5w!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png 848w, https://substackcdn.com/image/fetch/$s_!Dw5w!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png 1272w, https://substackcdn.com/image/fetch/$s_!Dw5w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62139ed7-6437-4503-9ffe-bc30d5b17d6f_1374x190.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><p>Revenons &#224; nos exemples initiaux :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hRdV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hRdV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png 424w, https://substackcdn.com/image/fetch/$s_!hRdV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png 848w, https://substackcdn.com/image/fetch/$s_!hRdV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png 1272w, https://substackcdn.com/image/fetch/$s_!hRdV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hRdV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png" width="960" height="384" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:384,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56964,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/172555033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hRdV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png 424w, https://substackcdn.com/image/fetch/$s_!hRdV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png 848w, https://substackcdn.com/image/fetch/$s_!hRdV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png 1272w, https://substackcdn.com/image/fetch/$s_!hRdV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F089a2559-b229-46dd-b2bd-559e1c28bdef_960x384.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>M&#234;me si l&#8217;on peut acc&#233;der &#224; n&#8217;importe quel message priv&#233; en tapant l&#8217;identifiant du<br>message dans l&#8217;URL, il est impossible qu&#8217;un utilisateur malveillant puisse deviner l&#8217;identifiant d&#8217;un message priv&#233; qu&#8217;il n&#8217;est pas cens&#233; voir.</p><p>Consid&#233;rez encore notre URL :</p><pre><code><code>/investments/38</code></code></pre><p>Avec un UUID, il devient</p><pre><code><code>/investments/019bff1a-a80f-7bad-b4da-ea545f04972c</code></code></pre><p>et le nombre d&#8217;investissements est d&#233;sormais cach&#233;.</p><p>Ainsi, les UUID, qu&#8217;ils soient de la version 4 ou de la version 7, s&#233;curisent votre<br>application depuis la base de donn&#233;es et obscurcissent des informations potentiellement sensibles.</p><p>&#8212; <em>Andrew, d&#233;veloppeur chez Capsens</em></p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/p/pourquoi-et-comment-on-est-passe?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Partager&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Si cet article vous a &#233;t&#233; utile, partagez-le &#224; un(e) coll&#232;gue.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/p/pourquoi-et-comment-on-est-passe?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Partager&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/p/pourquoi-et-comment-on-est-passe?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Partager</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[🍪🧪 Écrire des tests qui évitent les régressions]]></title><description><![CDATA[Temps de lecture : 8 minutes]]></description><link>https://www.rubybiscuit.fr/p/ecrire-des-tests-qui-evitent-les</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/ecrire-des-tests-qui-evitent-les</guid><pubDate>Wed, 04 Feb 2026 15:02:25 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4cfe2238-bc93-4bcc-8640-58094334d572_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la <strong>42&#232;me &#233;dition</strong> de Ruby Biscuit.<br>Vous &#234;tes maintenant <strong>610 abonn&#233;s</strong> &#129395;<br><br><strong>Annonce :</strong> <strong>On recrute ! Capsens cherche un&#183;e dev backend Ruby on Rails confirm&#233;&#183;e &#224; Lyon. Toutes les infos <a href="https://www.welcometothejungle.com/fr/companies/capsens/jobs/developpeur-se-confirme-backend-ruby-on-rails_lyon">ici</a></strong></p><p>Bonne lecture.</p><div><hr></div><p>Je me souviens d&#8217;une &#233;poque pas si lointaine, quand j&#8217;ai commenc&#233; ce m&#233;tier de d&#233;veloppeur web, o&#249; &#233;crire des tests automatiques, eh bien &#231;a n&#8217;existait tout simplement pas ! &#192; contrario, aujourd&#8217;hui, je ne connais plus personne qui n&#8217;en &#233;crit pas. Alors pourquoi ? &#192; quoi &#231;a sert les tests ? Et comment maximiser leur efficacit&#233; en minimisant l&#8217;effort n&#233;cessaire pour les &#233;crire ?</p><h3>&#192; quoi &#231;a sert ?</h3><p>Entre le monde d&#8217;avant les tests, et le monde d&#8217;apr&#232;s, une chose principalement est devenue beaucoup, beaucoup, plus facile : modifier son programme sans peur. Sans peur des effets de bord, sans avoir besoin de re-tester &#224; la main toutes les fonctionnalit&#233;s pour voir si on n&#8217;a pas introduit quelque part un bug inattendu. Sauf si vous pratiquez le TDD, les tests ne vous aident pas &#224; d&#233;velopper. Mieux vaut jouer des sc&#233;narios &#224; la main en local pour voir comment fonctionne ce que vous &#234;tes en train de coder plut&#244;t que d&#8217;&#233;crire des tests. Les tests ne font pas non plus une bonne documentation. Mieux vaut &#233;crire du code lisible et bien d&#233;coup&#233;, et vous n&#8217;aurez pas besoin de tests pour comprendre votre code. Les tests sont INDISPENSABLES pour une chose : v&#233;rifier que toutes les autres fonctionnalit&#233;s de l&#8217;app fonctionnent toujours apr&#232;s votre passage. Et c&#8217;est donc pour cette raison qu&#8217;il faut &#233;crire des tests; des tests qui r&#233;pondent &#224; cette probl&#233;matique !</p><h3>&#201;crire des tests qui &#233;vitent les r&#233;gressions !</h3><p>Dans la grande famille des tests, on peut diff&#233;rencier trois grands groupes : les tests dits &#8220;unitaires&#8221;, qui testent de tr&#232;s petits bouts de code (une seule m&#233;thode par exemple). Les tests dits &#8220;d&#8217;int&#233;gration&#8221;, qui testent plusieurs petits bouts de code en m&#234;me temps (une m&#233;thode qui appelle une m&#233;thode qui appelle une m&#233;thode). Et finalement les tests dits &#8220;end to end&#8221;, qui simulent l&#8217;ensemble de l&#8217;application, en simulant directement les interactions de l&#8217;utilisateur.</p><p>Afin de pr&#233;venir les bugs en production, le mieux serait d&#8217;&#233;crire des tests end to end. Ils testent toute la stack, tous les appels, tout ! Si un utilisateur en prod a un bug, un test end to end qui simule le m&#234;me parcours l&#8217;aurait forc&#233;ment vu (et donc &#233;vit&#233;). Sauf que les tests end to end c&#8217;est loooong et compliqu&#233;&#233;&#233;&#233; &#224; &#233;crire. Et c&#8217;est looooooong &#224; ex&#233;cuter aussi ! Franchement, quel d&#233;veloppeur peut se permettre d&#8217;attendre une heure (ou plus) &#224; chaque fois qu&#8217;il/elle lance un <code>rspec</code> ?</p><p>Les tests end-to-end ont clairement leur places dans votre suite de tests. En particulier pour les parties les plus sensibles ou les sc&#233;narios les plus communs de votre application. Mais ils ne constitueront pas la majorit&#233; de vos tests.</p><p>Pour cela, il nous reste donc deux pr&#233;tendants &#224; analyser, les tests unitaires et les tests d&#8217;int&#233;grations. Lesquels sont les meilleurs pour rep&#233;rer les bugs d&#8217;effets de bord ?</p><h3>Lesquels sont les meilleurs ?</h3><p>Imaginons que j&#8217;ai ces bouts de code dans mon appli Rails :</p><pre><code><code># app/models/user.rb</code>
<code>class User &lt; ApplicationRecord</code>
<code>  def send_welcome_email</code>
<code>    UserMailer.welcome(self).deliver_later</code>
<code>  end</code>
<code>end</code>

<code>#app/controllers/users_controller.rb</code>
<code>class UsersController &lt; ApplicationController</code>
<code>  def create</code>
<code>    @user = User.create(user_params)</code>
<code>    @user.send_welcome_email</code>
<code>  end</code>

<code>  def user_params</code>
<code>    params.require(:user).permit(:email)</code>
<code>  end</code>
<code>end</code></code></pre><p>Et les tests unitaires qui vont avec :</p><pre><code><code># spec/models/user_spec.rb</code>
<code>RSpec.describe User do</code>
<code>  describe "after_create callback" do</code>
<code>    it "#send_welcome_email" do</code>
<code>      user = build(:user)</code>

<code>      expect(UserMailer).to receive(:welcome)</code>

<code>      user.send_welcome_email</code>
<code>    end</code>
<code>  end</code>
<code>end</code>

<code># spec/controllers/users_controller_spec.rb</code>
<code>RSpec.describe UsersController do</code>
<code>  describe "POST #create" do</code>
<code>    it "sends an email" do</code>
<code>      user = instance_double(User)</code>

<code>      allow(User).to receive(:create).and_return(user)</code>
<code>      allow(user).to receive(:send_welcome_email)</code>

<code>      post :register, params: { email: "toto@email.fr" }</code>

<code>      expect(user).to have_received(:send_welcome_email)</code>
<code>    end</code>
<code>  end</code>
<code>end</code></code></pre><p>Jusqu&#8217;ici tout va bien. Les tests sont verts, tout fonctionne normalement, je peux envoyer mon code en production sans inqui&#233;tude.</p><p>Et puis, un beau jour, quelques mois plus tard, je cr&#233;e une version &#8220;API&#8221; de la cr&#233;ation d&#8217;utilisateurs, avec un twist :</p><pre><code><code># app/controllers/api/users_controller.rb</code>
<code>module API</code>
<code>  class UsersController &lt; APIController</code>
<code>    def create</code>
<code>      @user = User.create(user_params)</code>
<code>      @user.send_welcome_email</code>
<code>    end</code>

<code>    def user_params</code>
<code>      params.require(:user).permit(:email, :role)</code>
<code>    end</code>
<code>  end</code>
<code>end</code>

<code># app/models/user.rb</code>
<code>class User &lt; ApplicationRecord</code>
<code>  validates :role, inclusion: {in: %w[admin customer], allow_blank: true}</code>

<code>  def send_welcome_email</code>
<code>    UserMailer.welcome(self).deliver_later if role == "customer"</code>
<code>  end</code>
<code>end</code></code></pre><p>Mon API permet de g&#233;rer plusieurs &#8220;r&#244;les&#8221;, les clients et les admins. Et forc&#233;ment, je n&#8217;envoie pas d&#8217;emails aux admins, ils n&#8217;en ont pas besoin. Bon, maintenant il faut que je fasse mes tests unitaires. J&#8217;en rajoute un pour mon nouveau controller, et comme j&#8217;ai modifi&#233; le mod&#232;le bah je vais devoir modifier le test pour cette m&#233;thode.</p><pre><code><code># spec/controllers/users_controller_spec.rb</code>
<code>RSpec.describe API::UsersController do</code>
<code>  describe "POST #create" do</code>
<code>    it "sends an email" do</code>
<code>      user = instance_double(User)</code>

<code>      allow(User).to receive(:create).and_return(user)</code>
<code>      allow(user).to receive(:send_welcome_email)</code>

<code>      post :register, params: { email: "toto@email.fr", role: "customer" }</code>

<code>      expect(user).to have_received(:send_welcome_email)</code>
<code>    end</code>
<code>  end</code>
<code>end</code>

<code># spec/models/user_spec.rb</code>
<code>RSpec.describe User do</code>
<code>  describe "after_create callback" do</code>
<code>    it "#send_welcome_email" do</code>
<code>      user = build(:user, role: "customer")</code>

<code>      expect(UserMailer).to receive(:welcome)</code>

<code>      user.send_welcome_email</code>
<code>    end</code>
<code>  end</code>
<code>end</code></code></pre><p>Super chouette. J&#8217;ai fini mon code, tous les tests sont verts, je peux envoyer sereinement mon code en production&#8230;</p><p>Et l&#224; &#8230;</p><p>En fait, personne ne le remarque vraiment, et c&#8217;est gr&#226;ce aux clients qui commencent &#224; se plaindre au service client qu&#8217;on s&#8217;en rend compte : les utilisateurs qui s&#8217;inscrivent par la voie &#8220;classique&#8221; ne re&#231;oivent plus l&#8217;email de bienvenue (qui contient plein d&#8217;infos importantes).</p><p>Et mon chef me demande pourquoi je ne m&#8217;en suis pas rendu compte avant alors m&#234;me qu&#8217;on alloue 40% du temps &#224; &#233;crire des tests !</p><p>Si seulement j&#8217;avais &#233;crit un test d&#8217;int&#233;gration plut&#244;t qu&#8217;un test unitaire, par exemple :</p><pre><code><code># spec/controllers/users_controller_spec.rb</code>
<code>RSpec.describe UsersController do</code>
<code>  describe "POST #create" do</code>
<code>    it "sends an email" do</code>
<code>      expect {</code>
<code>        post "/users", params: { user: {email: "toto@email.fr"} }</code>
<code>      }.to change { ActionMailer::Base.deliveries.count }.by(1)</code>
<code>    end</code>
<code>  end</code>
<code>end</code></code></pre><p>Celui-ci aurait rat&#233; quand j&#8217;ai introduit la notion de r&#244;les. Parce qu&#8217;il n&#8217;a pas de mock. Parce les tests d&#8217;int&#233;gration &#233;vitent une grande partie des mocks, alors que les tests unitaires en utilisent beaucoup pour limiter le scope de ce qu&#8217;ils testent. Et que les mocks il faut les maintenir. Et que cette fois-ci j&#8217;ai oubli&#233;.</p><p>Premier point marqu&#233; en faveur des tests d&#8217;int&#233;gration. Continuons avec un deuxi&#232;me exemple.</p><h3>Le deuxi&#232;me exemple ?</h3><p>Mettons que j&#8217;ai un service :</p><pre><code><code># app/services/order_total.rb</code>
<code>class OrderTotal</code>
<code>  def initialize(order)</code>
<code>    @order = order</code>
<code>  end</code>

<code>  def call</code>
<code>    @order.items.sum(&amp;:price)</code>
<code>  end</code>
<code>end</code>

<code># spec/services/order_total_spec.rb</code>
<code>describe OrderTotal do</code>
<code>  it "returns the sum of item prices" do</code>
<code>    order = double(items: [</code>
<code>      double(price: 10),</code>
<code>      double(price: 20)</code>
<code>    ])</code>

<code>    expect(OrderTotal.new(order).call).to eq(30)</code>
<code>  end</code>
<code>end</code></code></pre><p>Et un controller :</p><pre><code><code># app/controllers/orders_controller.rb</code>
<code>class OrdersController &lt; ApplicationController</code>
<code>  def show</code>
<code>    order = Order.find(params[:id])</code>
<code>    total = OrderTotal.new(order).call</code>

<code>    render json: { total: total.to_i }</code>
<code>  end</code>
<code>end</code>

<code># spec/controllers/orders_controller_spec.rb</code>
<code>describe OrdersController do</code>
<code>  it "returns the order total" do</code>
<code>    order = double(id: 1)</code>
<code>    allow(Order).to receive(:find).and_return(order)</code>

<code>    allow(OrderTotal).to receive_message_chain(:new, :call)</code>
<code>      .and_return(30)</code>

<code>    get :show, params: { id: 1 }</code>

<code>    expect(response.body).to include("30")</code>
<code>  end</code>
<code>end</code></code></pre><p>Maintenant, notre app &#233;volue et on doit rajouter le calcul de la TVA. Comme notre code est bien d&#233;coup&#233;, c&#8217;est facile, il suffit de modifier le service :</p><pre><code><code># app/services/order_total.rb</code>
<code>class OrderTotal</code>
<code>  def initialize(order)</code>
<code>    @order = order</code>
<code>  end</code>

<code>  def call</code>
<code>    {</code>
<code>      subtotal: subtotal,</code>
<code>      tax: tax,</code>
<code>      total: subtotal + tax</code>
<code>    }</code>
<code>  end</code>

<code>  private</code>

<code>  def subtotal</code>
<code>    @order.items.sum(&amp;:price)</code>
<code>  end</code>

<code>  def tax</code>
<code>    subtotal * 0.2</code>
<code>  end</code>
<code>end</code>

<code># spec/services/order_total_spec.rb</code>
<code>describe OrderTotal do</code>
<code>  it "returns a detailed total hash" do</code>
<code>    order = double(items: [</code>
<code>      double(price: 10),</code>
<code>      double(price: 20)</code>
<code>    ])</code>

<code>    result = OrderTotal.new(order).call</code>

<code>    expect(result[:total]).to eq(36)</code>
<code>  end</code>
<code>end</code></code></pre><p>Et maintenant, deux options : soit on a &#233;crit des tests unitaires, comme je l&#8217;ai fait dans cet exemple. Dans ce cas les tests sont tous verts, et je vais soit leur faire confiance et envoyer un bug en production, soit ne pas leur faire confiance et rechercher manuellement tous les endroits qui auraient pu &#234;tre impact&#233;s par ma modif (et dans ce cas, j&#8217;ai le risque d&#8217;en oublier, et aussi, pourquoi je m&#8217;emb&#234;te &#224; &#233;crire des tests ?).</p><p>Ou bien, j&#8217;ai &#233;crit des tests d&#8217;int&#233;gration, comme celui-ci :</p><pre><code><code># spec/controllers/orders_controller_spec.rb</code>
<code>describe "GET /orders/:id" do</code>
<code>  it "returns the correct total" do</code>
<code>    order = create(:order)</code>
<code>    create(:item, order: order, price: 10)</code>
<code>    create(:item, order: order, price: 20)</code>

<code>    get "/orders/#{order.id}"</code>

<code>    json = JSON.parse(response.body)</code>
<code>    expect(json["total"]).to eq(36)</code>
<code>  end</code>
<code>end</code></code></pre><p>Et le test serait devenu rouge. Et je me serai rendu compte que maintenant le service <code>OrderTotal</code> me renvoie un <code>Hash</code>, et que <code>{}.to_i</code> &#231;a renvoie <code>0</code> !</p><p>Un second point pour les tests d&#8217;int&#233;gration, qui nous ont &#233;vit&#233; de grosses gouttes de sueur.</p><h3>Comment &#233;crire de bons tests d&#8217;int&#233;gration</h3><p>Avant de conclure, j&#8217;aimerais attirer votre attention sur un point important : les tests d&#8217;int&#233;gration ne vous sauveront la mise que s&#8217;ils sc&#233;narisent toutes les variantes possibles du code.</p><p>Prenez cet exemple, j&#8217;ai des documents associ&#233;s &#224; mes utilisateurs, et un m&#233;canisme d&#8217;autorisations qui limite l&#8217;acc&#232;s d&#8217;un utilisateur &#224; ses propres documents uniquement.</p><pre><code><code># app/models/document.rb</code>
<code>class Document &lt; ApplicationRecord</code>
<code>  belongs_to :user</code>
<code>end</code>

<code># app/policies/document_policy.rb</code>
<code>class DocumentPolicy</code>
<code>  def initialize(user, document)</code>
<code>    @user = user</code>
<code>    @document = document</code>
<code>  end</code>

<code>  def show?</code>
<code>    @document.public? || owner?</code>
<code>  end</code>

<code>  private</code>

<code>  def owner?</code>
<code>    @document.user_id = @user.id</code>
<code>  end</code>
<code>end</code>

<code># app/controllers/documents_controller.rb</code>
<code>def show</code>
<code>  document = Document.find(params[:id])</code>
<code>  authorize document</code>

<code>  render json: document</code>
<code>end</code></code></pre><p>Et le test d&#8217;int&#233;gration qui va avec :</p><pre><code><code># spec/controllers/documents_controller_spec.rb</code>
<code>describe "GET /documents/:id" do</code>
<code>  it "allows the owner to see the document" do</code>
<code>    user = create(:user)</code>
<code>    document = create(:document, user: user, public: false)</code>

<code>    sign_in user</code>
<code>    get "/documents/#{document.id}"</code>

<code>    expect(response).to have_http_status(:ok)</code>
<code>  end</code>
<code>end</code></code></pre><p>Vous, cher lecteur, aurez peut-&#234;tre vu le bug (dans la m&#233;thode <code>DocumentPolicy#owner?</code>, le d&#233;veloppeur a confondu le symbole d&#8217;une assignation <code>=</code> avec le symbole d&#8217;une &#233;galit&#233; <code>==</code>), mais mon test d&#8217;int&#233;gration lui ne verra rien du tout. Pourquoi ? Parce que je ne teste que le &#8220;chemin heureux&#8221;, le cas o&#249; tout va bien. Pour bien faire, il FAUT que j&#8217;ajoute un test qui v&#233;rifie quand &#231;a ne fonctionne pas :</p><pre><code><code># spec/controllers/documents_controller_spec.rb</code>
<code>describe "GET /documents/:id" do</code>
<code>  it "allows the owner to see the document" do</code>
<code>    user = create(:user)</code>
<code>    other_user = create(:user)</code>
<code>    document = create(:document, user: other_user, public: false)</code>

<code>    sign_in user</code>
<code>    get "/documents/#{document.id}"</code>

<code>    expect(response).to have_http_status(:forbidden)</code>
<code>  end</code>
<code>end</code></code></pre><p>&#192; cette condition seulement je peux me fier &#224; mes tests d&#8217;int&#233;gration. C&#8217;est donc une n&#233;cessit&#233; absolue de v&#233;rifier et rev&#233;rifier que vos tests d&#8217;int&#233;gration couvrent correctement tous les diff&#233;rents cas qui pourraient se produire. Faites relire vos tests, demandez au relecteur de se concentre particuli&#232;rement fort sur les tests, pour v&#233;rifier que vous n&#8217;avez rien laiss&#233; passer.</p><p>Vos tests ne sont utiles QUE si vous leur faites confiance. Et pour leur faire confiance, il faut qu&#8217;ils d&#233;tectent les bugs, et qu&#8217;ils testent exhaustivement ce qui se passe dans votre code.</p><h3>Les tests unitaires sont-ils utiles ?</h3><p>&#192; me lire, vous pourriez commencer par croire que les tests unitaires ne servent &#224; rien. C&#8217;est bien normal car j&#8217;ai &#233;crit cet article pour argumenter que les tests d&#8217;int&#233;gration sont plus int&#233;ressants que les tests unitaires.</p><p>Afin d&#8217;apporter un peu de nuance dans mon propos, discutons autour d&#8217;un exemple. Prenons le code suivant :</p><pre><code><code># app/controllers/subscriptions_controller.rb</code>
<code>def create</code>
<code>  @subscription = Subscription.new(subscription_params)</code>
<code>  </code>
<code>  CheckSubscriptionValidity.new(@subscription)</code>
<code>  SubmitSubscriptionToGoverment.new(@subscription)</code>
<code>  NotifySubscriptionHolder.new(@subscription)</code>
<code>  @subscription.save</code>

<code>  render @subscription</code>
<code>end</code></code></pre><p>&#192; votre avis, quelle suite de test sera la plus simple et rapide &#224; &#233;crire, relire et maintenir :</p><ul><li><p>Option A : Des tests unitaires pour chacun des trois services qui sont utilis&#233;s dans cette action, plus un ou deux tests d&#8217;int&#233;gration qui v&#233;rifient que l&#8217;ensemble fonctionne correctement.</p></li><li><p>Option B : Des tests d&#8217;int&#233;gration uniquement, autant qu&#8217;il faut pour couvrir toutes les combinaisons possibles et imaginables.</p></li></ul><p>Dans cette situation aucune des deux options ne se d&#233;marque vraiment. Vous pourriez choisir l&#8217;option A, je pourrais choisir l&#8217;option B, et nous aurions tous les deux raison.</p><h3>Conclusion</h3><p>Peu importe que vous &#233;criviez des tests unitaires, d&#8217;int&#233;gration ou end-to-end, mais pour &#233;crire de bons tests, ce que je veux vous dire c&#8217;est :</p><ul><li><p>Couvrez TOUS les sc&#233;narios</p></li><li><p>Utilisez les tests qui simulent au plus pr&#232;s le comportement d&#8217;un utilisateur r&#233;el</p></li><li><p>Autant que possible en prenant en compte les limitation de temps et de ressources dont vous disposez</p></li></ul><p>Si vous faites cela, vous pourrez enfin modifier votre application sans peur, et arr&#234;ter d&#8217;&#8217;envoyer des bugs en production.</p><h3>Outro</h3><p>Et voil&#224; ! Merci &#224; tous d&#8217;avoir lu mon avis autour de la politique de tests dans une &#233;quipe technique. J&#8217;esp&#232;re que j&#8217;ai su apporter des arguments convaincants et que cet article sera la source de nombreux d&#233;bats &#224; travers toutes les bo&#238;tes tech de France. Prenez soin de vos coll&#232;gues et &#224; bient&#244;t !</p><p>&#8212; <em><a href="https://www.linkedin.com/in/thomas-petrachi-b2b1a428/">Thomas</a></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🧬 Créer une API Web simplement et rapidement]]></title><description><![CDATA[Temps de lecture : 8 minutes]]></description><link>https://www.rubybiscuit.fr/p/creer-une-api-web-simplement-et-rapidement</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/creer-une-api-web-simplement-et-rapidement</guid><pubDate>Wed, 07 Jan 2026 15:03:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7ed6c9e4-7a0c-4cda-82a6-af2ffccad76e_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits, bonne ann&#233;e 2026 &#224; vous ! &#129395;</p><p>Bienvenue sur la <strong>41&#232;me &#233;dition</strong> de Ruby Biscuit.<br>Vous &#234;tes maintenant <strong>612 abonn&#233;s</strong> &#129395;</p><p>Bonne lecture.</p><div><hr></div><h2>Cr&#233;er une API Web simplement et rapidement</h2><p>Ce mois-ci, nous allons plonger dans la cr&#233;ation d&#8217;une API. Bien structurer son API est crucial pour votre application. D&#233;couvrez des techniques qui peuvent am&#233;liorer la maintenabilit&#233; et les performances de vos projets.</p><div><hr></div><p><strong>Partie th&#233;orique</strong></p><p>Avant de nous lancer, faisons un bref rappel sur les APIs et leurs r&#244;les dans une application web.</p><p>Une API web va exposer vos donn&#233;es &#224; l&#8217;ext&#233;rieur de l&#8217;application. Elle va permettre aussi de communiquer avec d&#8217;autres services web.</p><p>Il est consid&#233;r&#233; comme une bonne pratique de :</p><ul><li><p>versionner vos API,</p></li><li><p>prot&#233;ger leurs acc&#232;s (authentification et doorkeeper),</p></li><li><p>paginer leurs r&#233;ponses (pagy),</p></li><li><p>avoir une documentation (Rswag),</p></li><li><p>respecter le format JSON Api et les formats ISO (jsonapi-serilizer).</p></li></ul><div><hr></div><p><strong>Setup</strong></p><p>Apr&#232;s ce bref rappel, passons &#224; l&#8217;impl&#233;mentation.</p><p>Tout d&#8217;abord, voici les gems que nous allons utiliser tout au long de cet article.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4IIj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4IIj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png 424w, https://substackcdn.com/image/fetch/$s_!4IIj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png 848w, https://substackcdn.com/image/fetch/$s_!4IIj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png 1272w, https://substackcdn.com/image/fetch/$s_!4IIj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4IIj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png" width="483" height="172.06875" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:342,&quot;width&quot;:960,&quot;resizeWidth&quot;:483,&quot;bytes&quot;:30791,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4IIj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png 424w, https://substackcdn.com/image/fetch/$s_!4IIj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png 848w, https://substackcdn.com/image/fetch/$s_!4IIj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png 1272w, https://substackcdn.com/image/fetch/$s_!4IIj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fecb6d4-26f1-4bf4-9255-c0aa06c504fb_960x342.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p><p>Dans cet article, je n&#8217;aborde pas l&#8217;authentification. Il existe plusieurs fa&#231;ons de la g&#233;rer, et cela pourrait faire l&#8217;objet d&#8217;un article &#224; part. Il ne faut cependant pas l&#8217;oublier, sous peine d&#8217;exposer vos donn&#233;es &#224; n&#8217;importe qui.</p><p>Nous allons maintenant cr&#233;er un controleur parent pour les futurs contr&#244;leurs d&#8217;API de l&#8217;application.</p><p>Il existe deux &#233;coles pour nommer un contr&#244;leur parent dans un namespace : <code>BaseController</code> ou <code>ApplicationController</code>. Ici, nous allons ici choisir <code>ApplicationController</code>.</p><p>Ensuite, je vais paginer mes ressources avec la gem <a href="https://github.com/ddnexus/pagy">Pagy</a>. Par d&#233;faut, je choisis de renvoyer 50 ressources par appel API. On pourrait laisser aux utilisateurs la possibilit&#233; de changer cette valeur dans la payload, mais par soucis de simplicit&#233; on ne va pas le faire ici.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!77h2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!77h2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png 424w, https://substackcdn.com/image/fetch/$s_!77h2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png 848w, https://substackcdn.com/image/fetch/$s_!77h2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png 1272w, https://substackcdn.com/image/fetch/$s_!77h2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!77h2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png" width="1380" height="918" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:918,&quot;width&quot;:1380,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:126999,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!77h2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png 424w, https://substackcdn.com/image/fetch/$s_!77h2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png 848w, https://substackcdn.com/image/fetch/$s_!77h2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png 1272w, https://substackcdn.com/image/fetch/$s_!77h2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4da6f18-b61e-432d-ab04-d89ad0b06f85_1380x918.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p><strong>Impl&#233;mentation pour le mod&#232;le Project</strong></p><p>Comme dans de nombreuses applications web, nous avons un mod&#232;le <code>Project</code>. Commen&#231;ons par cr&#233;er un index pour les projets dans l&#8217;API.</p><p>Il faut donc ajouter une route dans le namespace <code>api</code>, puis dans le namespace <code>v1</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YBBM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YBBM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png 424w, https://substackcdn.com/image/fetch/$s_!YBBM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png 848w, https://substackcdn.com/image/fetch/$s_!YBBM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png 1272w, https://substackcdn.com/image/fetch/$s_!YBBM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YBBM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png" width="960" height="486" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/736afcfe-4066-4ca0-be70-785500087a67_960x486.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:486,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44468,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YBBM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png 424w, https://substackcdn.com/image/fetch/$s_!YBBM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png 848w, https://substackcdn.com/image/fetch/$s_!YBBM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png 1272w, https://substackcdn.com/image/fetch/$s_!YBBM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736afcfe-4066-4ca0-be70-785500087a67_960x486.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p><strong>Contr&#244;leur Projects</strong></p><p>Nous allons maintenant cr&#233;er le contr&#244;leur Projects.</p><p>Nous appliquons d&#8217;abord la pagination gr&#226;ce &#224; la m&#233;thode <code>pagy()</code> fournie par la gem. Elle re&#231;oit comme premier argument nos ressources, puis la valeur de pagination.</p><p>Ensuite nous allons renvoyer un json qui contient nos donn&#233;es, des liens vers les pages (<em>links</em>) et quelques metadonn&#233;es conform&#233;ment &#224; la spec <a href="https://jsonapi.org/">JSON API</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gL1i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gL1i!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png 424w, https://substackcdn.com/image/fetch/$s_!gL1i!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png 848w, https://substackcdn.com/image/fetch/$s_!gL1i!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png 1272w, https://substackcdn.com/image/fetch/$s_!gL1i!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gL1i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png" width="1092" height="1350" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1350,&quot;width&quot;:1092,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:172462,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gL1i!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png 424w, https://substackcdn.com/image/fetch/$s_!gL1i!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png 848w, https://substackcdn.com/image/fetch/$s_!gL1i!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png 1272w, https://substackcdn.com/image/fetch/$s_!gL1i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc81fdb7-7e43-4f71-9d96-b6c612e79588_1092x1350.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Serializer</strong></p><p>Pour respecter le principe de responsabilit&#233; unique, nous g&#233;n&#233;rons donc le hash dans un Serializer avec la <a href="https://github.com/jsonapi-serializer/jsonapi-serializer">gem jsonapi-serializer</a>. Sa mission est de transformer la donn&#233;e brute en un format clair et lisible. La gem va aussi respecter les sp&#233;cifications de <a href="https://jsonapi.org/">JSON API.</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BDH1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BDH1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png 424w, https://substackcdn.com/image/fetch/$s_!BDH1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png 848w, https://substackcdn.com/image/fetch/$s_!BDH1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png 1272w, https://substackcdn.com/image/fetch/$s_!BDH1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BDH1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png" width="1400" height="1302" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1302,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:199880,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BDH1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png 424w, https://substackcdn.com/image/fetch/$s_!BDH1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png 848w, https://substackcdn.com/image/fetch/$s_!BDH1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png 1272w, https://substackcdn.com/image/fetch/$s_!BDH1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c868379-6f42-4a53-9259-7e5d3a91004a_1400x1302.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!I_VR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!I_VR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png 424w, https://substackcdn.com/image/fetch/$s_!I_VR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png 848w, https://substackcdn.com/image/fetch/$s_!I_VR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png 1272w, https://substackcdn.com/image/fetch/$s_!I_VR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!I_VR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png" width="1014" height="870" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:870,&quot;width&quot;:1014,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:103960,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!I_VR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png 424w, https://substackcdn.com/image/fetch/$s_!I_VR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png 848w, https://substackcdn.com/image/fetch/$s_!I_VR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png 1272w, https://substackcdn.com/image/fetch/$s_!I_VR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b9cda6a-9798-4eb0-b255-5e59830420e8_1014x870.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p><strong>Format des donn&#233;es en g&#233;n&#233;ral et des dates en particulier (avec le serializer)</strong></p><p>Respectez la norme <a href="https://www.openapis.org/">OpenAPI</a> pour le format des donn&#233;es. Notamment pour les dates utilisez la m&#233;thode <code>iso8601</code> .</p><p>Par exemple, le 13 mai 2025 devra s&#8217;&#233;crire <code>"2025-05-13"</code> ou <code>"2025-05-13T14:45:00+02:00"</code>.</p><div><hr></div><p><strong>Rendu</strong><br><br>Voici un exemple d&#8217;un r&#233;sultat d&#8217;une requ&#234;te faite avec <a href="https://www.postman.com/">Postman</a> sur l&#8217;endpoint <code>http://localhost:3000/api/v1/projects</code> (en changeant la pagination &#224; 2)</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s3AA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s3AA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png 424w, https://substackcdn.com/image/fetch/$s_!s3AA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png 848w, https://substackcdn.com/image/fetch/$s_!s3AA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png 1272w, https://substackcdn.com/image/fetch/$s_!s3AA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s3AA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png" width="1380" height="2502" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2502,&quot;width&quot;:1380,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:395896,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s3AA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png 424w, https://substackcdn.com/image/fetch/$s_!s3AA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png 848w, https://substackcdn.com/image/fetch/$s_!s3AA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png 1272w, https://substackcdn.com/image/fetch/$s_!s3AA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5e7ed31-4c7a-46b9-89a9-abccdf881a8d_1380x2502.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><p><strong>Optimisation : Requ&#234;te N+1</strong></p><p>Attention aux requ&#234;tes N+1 dans vos contr&#244;leurs !</p><p>Si votre <em>Serializer</em> utilise des donn&#233;es provenant d&#8217;autres tables, pensez &#224; les inclure.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B6Y1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B6Y1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png 424w, https://substackcdn.com/image/fetch/$s_!B6Y1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png 848w, https://substackcdn.com/image/fetch/$s_!B6Y1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png 1272w, https://substackcdn.com/image/fetch/$s_!B6Y1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B6Y1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png" width="976" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:342,&quot;width&quot;:976,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:43637,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!B6Y1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png 424w, https://substackcdn.com/image/fetch/$s_!B6Y1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png 848w, https://substackcdn.com/image/fetch/$s_!B6Y1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png 1272w, https://substackcdn.com/image/fetch/$s_!B6Y1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe69f8474-fcc9-4d13-8fd0-7fc12bfa160d_976x342.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p><strong>Tests</strong></p><p>Assurez-vous de la fiabilit&#233; de votre code gr&#226;ce &#224; des tests.</p><p>Dans notre cas, nous allons &#233;crire deux tests :</p><ul><li><p>un test du <code>ProjectSerializer</code>,</p></li><li><p>un test de requ&#234;tes sur l&#8217;API.</p></li></ul><p>Dans un premier temps, nous testons que le <em>Serializer</em> renvoie les bonnes donn&#233;es.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XF-Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XF-Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png 424w, https://substackcdn.com/image/fetch/$s_!XF-Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png 848w, https://substackcdn.com/image/fetch/$s_!XF-Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png 1272w, https://substackcdn.com/image/fetch/$s_!XF-Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XF-Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png" width="1456" height="3987" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3987,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:809773,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/183673853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XF-Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png 424w, https://substackcdn.com/image/fetch/$s_!XF-Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png 848w, https://substackcdn.com/image/fetch/$s_!XF-Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png 1272w, https://substackcdn.com/image/fetch/$s_!XF-Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7df7c341-f169-40b8-a8d3-5f19dc57a5ee_1650x4518.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Pour le test d&#8217;API et la document nous allons utiliser Swagger mais nous verrons cela dans un prochain article</p><p><strong>Conclusion</strong></p><p>Et voil&#224;, vous venez de construire une API simplement et rapidement. <br>Bonne ann&#233;e 2026 !</p><p>&#8212; <em>Alexandre</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪📈 Le fichier que tous nos devs devraient avoir dans leur machine et que personne ne mentionne]]></title><description><![CDATA[Temps de lecture : 6 minutes]]></description><link>https://www.rubybiscuit.fr/p/booster-votre-productivite-avec-irbrc</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/booster-votre-productivite-avec-irbrc</guid><pubDate>Wed, 03 Dec 2025 15:03:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/94bbde9e-6312-4beb-8998-f32289069e21_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la <strong>40&#232;me &#233;dition</strong> de Ruby Biscuit.<br>Vous &#234;tes maintenant <strong>601 abonn&#233;s</strong> &#129395;</p><p>Bonne lecture.</p><div><hr></div><h1>Booster votre productivit&#233; avec <code>.irbrc</code></h1><p>En tant que d&#233;veloppeur RoR je passe ma vie avec un terminal ouvert. Il y a quelques ann&#233;es, je me suis aper&#231;u que beaucoup trop souvent je tapais les m&#234;mes choses dedans. Mais aujourd&#8217;hui c&#8217;est beaucoup moins vrai. Et je me suis dit qu&#8217;il &#233;tait grand temps de vous faire part de mes petites astuces &#128521;</p><div><hr></div><h2>Le setup en 10 secondes</h2><p>Plut&#244;t que d&#8217;expliquer le probl&#232;me, on va directement passer &#224; la solution car c&#8217;est tellement rapide, d&#8217;ailleurs il ne reste plus que 7 secondes !</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YhGg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YhGg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png 424w, https://substackcdn.com/image/fetch/$s_!YhGg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png 848w, https://substackcdn.com/image/fetch/$s_!YhGg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png 1272w, https://substackcdn.com/image/fetch/$s_!YhGg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YhGg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png" width="960" height="198" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:198,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18185,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YhGg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png 424w, https://substackcdn.com/image/fetch/$s_!YhGg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png 848w, https://substackcdn.com/image/fetch/$s_!YhGg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png 1272w, https://substackcdn.com/image/fetch/$s_!YhGg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cbc495a-aa3e-4fa1-8610-094716251db3_960x198.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Et voil&#224;! &#127881; Maintenant quand vous lancerez irb, vous aurez un joli message pour vous dire que votre <code>.irbrc</code> est charg&#233;.</p><p>Je vous entends d&#8217;ici. &#8220;Mais moi je n&#8217;utilise jamais irb&#8221;. Mais est-ce que par hasard vous utilisez de temps en temps un petit <code>rails console</code> dit <code>rails c</code> pour les intimes ?<br>Car cela fonctionne aussi!</p><p>&#9888;&#65039; Si jamais cela ne marche pas chez vous, c&#8217;est certainement que vous utilisez <code>pry</code> et non <code>irb</code>. Il suffit de cr&#233;er un <code>.pryrc</code> avec ceci dedans et c&#8217;est r&#233;gl&#233; :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0oHO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0oHO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png 424w, https://substackcdn.com/image/fetch/$s_!0oHO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png 848w, https://substackcdn.com/image/fetch/$s_!0oHO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png 1272w, https://substackcdn.com/image/fetch/$s_!0oHO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0oHO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png" width="960" height="198" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:198,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:16638,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0oHO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png 424w, https://substackcdn.com/image/fetch/$s_!0oHO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png 848w, https://substackcdn.com/image/fetch/$s_!0oHO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png 1272w, https://substackcdn.com/image/fetch/$s_!0oHO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F993cab57-ac25-436e-9c7c-65b1d252282f_960x198.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p><h2>C&#8217;est parti pour le boost &#128640;</h2><p>Je vais vous donner quelques exemples que j&#8217;ai glan&#233; &#224; droite et &#224; gauche, ainsi que quelques uns perso.</p><div><hr></div><h3>Me</h3><p>Dans quasi toutes les applications j&#8217;ai un model <code>User</code> et tr&#232;s souvent j&#8217;existe moi-m&#234;me dans les utilisateurs.<br>Donc je me retrouve souvent &#224; faire des :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gMIt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gMIt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png 424w, https://substackcdn.com/image/fetch/$s_!gMIt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png 848w, https://substackcdn.com/image/fetch/$s_!gMIt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png 1272w, https://substackcdn.com/image/fetch/$s_!gMIt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gMIt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png" width="960" height="246" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:246,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:23167,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gMIt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png 424w, https://substackcdn.com/image/fetch/$s_!gMIt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png 848w, https://substackcdn.com/image/fetch/$s_!gMIt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png 1272w, https://substackcdn.com/image/fetch/$s_!gMIt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b22167a-cbb5-47e6-8376-0d48e7357ce1_960x246.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Quand je reste quelques temps dans une entreprise, je finis par conna&#238;tre mon id par c&#339;ur donc cela va un peu plus vite :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oCcm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oCcm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png 424w, https://substackcdn.com/image/fetch/$s_!oCcm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png 848w, https://substackcdn.com/image/fetch/$s_!oCcm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png 1272w, https://substackcdn.com/image/fetch/$s_!oCcm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oCcm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png" width="960" height="246" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:246,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18170,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oCcm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png 424w, https://substackcdn.com/image/fetch/$s_!oCcm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png 848w, https://substackcdn.com/image/fetch/$s_!oCcm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png 1272w, https://substackcdn.com/image/fetch/$s_!oCcm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fb44f68-a1ec-4259-bbab-e05c8b02756a_960x246.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Mais maintenant j&#8217;ai cela dans mon <code>.irbrc </code>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rZG3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rZG3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png 424w, https://substackcdn.com/image/fetch/$s_!rZG3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png 848w, https://substackcdn.com/image/fetch/$s_!rZG3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png 1272w, https://substackcdn.com/image/fetch/$s_!rZG3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rZG3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png" width="960" height="390" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/73786906-55ff-4cc2-955d-42a3905657fc_960x390.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:390,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:30543,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rZG3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png 424w, https://substackcdn.com/image/fetch/$s_!rZG3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png 848w, https://substackcdn.com/image/fetch/$s_!rZG3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png 1272w, https://substackcdn.com/image/fetch/$s_!rZG3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73786906-55ff-4cc2-955d-42a3905657fc_960x390.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Donc dans mon terminal je peux faire: <code>me.projects.last</code> au lieu de <code>User.find_by(email: &#8216;mon@email.com&#8217;).projects.last</code></p><div><hr></div><h3>Models</h3><p>Cela m&#8217;arrive quelques fois d&#8217;&#234;tre sur une application et d&#8217;avoir besoin de lister tous les models.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Xkew!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Xkew!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png 424w, https://substackcdn.com/image/fetch/$s_!Xkew!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png 848w, https://substackcdn.com/image/fetch/$s_!Xkew!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png 1272w, https://substackcdn.com/image/fetch/$s_!Xkew!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Xkew!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png" width="960" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:342,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39475,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Xkew!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png 424w, https://substackcdn.com/image/fetch/$s_!Xkew!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png 848w, https://substackcdn.com/image/fetch/$s_!Xkew!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png 1272w, https://substackcdn.com/image/fetch/$s_!Xkew!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f501e57-9004-4fdc-9746-ca4d1357f876_960x342.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Avoir la liste des models en soi n&#8217;apporte pas beaucoup plus que de parcourir le dossier models, mais si je veux facilement lister tous les models qui ont le module <code>Taggable</code> je peux faire cela :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!igor!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!igor!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png 424w, https://substackcdn.com/image/fetch/$s_!igor!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png 848w, https://substackcdn.com/image/fetch/$s_!igor!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png 1272w, https://substackcdn.com/image/fetch/$s_!igor!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!igor!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png" width="960" height="198" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9093151d-985d-42d5-9c83-b032e0960875_960x198.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:198,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17057,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!igor!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png 424w, https://substackcdn.com/image/fetch/$s_!igor!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png 848w, https://substackcdn.com/image/fetch/$s_!igor!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png 1272w, https://substackcdn.com/image/fetch/$s_!igor!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9093151d-985d-42d5-9c83-b032e0960875_960x198.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><blockquote><p>Mais c&#8217;est quoi ce &#8216;it&#8217; ? &#128073; <a href="https://www.captainruby.fr/posts/le-parametre-it-de-ruby-3-4">Le param&#232;tre&#8221;it&#8221; de Ruby 3.4</a></p></blockquote><p>Ou lister tous les models qui ont une relation avec le model User :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7SrW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7SrW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png 424w, https://substackcdn.com/image/fetch/$s_!7SrW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png 848w, https://substackcdn.com/image/fetch/$s_!7SrW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png 1272w, https://substackcdn.com/image/fetch/$s_!7SrW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7SrW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png" width="1266" height="198" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:198,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26809,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7SrW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png 424w, https://substackcdn.com/image/fetch/$s_!7SrW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png 848w, https://substackcdn.com/image/fetch/$s_!7SrW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png 1272w, https://substackcdn.com/image/fetch/$s_!7SrW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3da71311-c71c-4894-bf3f-99f082e26386_1266x198.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Dans mon application derni&#232;rement je voulais savoir s&#8217;il y avait des models qui avaient la colonne <code>comment</code> &#224; nil. Mais je sais que j&#8217;ai plus de 10 models qui avaient une colonne <code>comments</code>. Pas de souci :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!h1_R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!h1_R!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png 424w, https://substackcdn.com/image/fetch/$s_!h1_R!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png 848w, https://substackcdn.com/image/fetch/$s_!h1_R!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png 1272w, https://substackcdn.com/image/fetch/$s_!h1_R!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!h1_R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png" width="1456" height="142" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:142,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:36649,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!h1_R!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png 424w, https://substackcdn.com/image/fetch/$s_!h1_R!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png 848w, https://substackcdn.com/image/fetch/$s_!h1_R!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png 1272w, https://substackcdn.com/image/fetch/$s_!h1_R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F772d7578-a52c-4c17-9041-e010e517d88b_2036x198.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h3>Multi-tenants</h3><blockquote><p>Kesako ? &#128073; <a href="https://github.com/ErwinM/acts_as_tenant?tab=readme-ov-file#acts-as-tenant">Gem Act At Tenant</a></p></blockquote><p>Dans un projet j&#8217;ai du multi tenants. Lors d&#8217;une requ&#234;te http pas de souci, mais en console, avant m&#234;me de pouvoir faire quoique ce soit, je suis oblig&#233; de set le tenant.</p><p>&#192; chaque fois je dois faire :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DYkR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DYkR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png 424w, https://substackcdn.com/image/fetch/$s_!DYkR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png 848w, https://substackcdn.com/image/fetch/$s_!DYkR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png 1272w, https://substackcdn.com/image/fetch/$s_!DYkR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DYkR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png" width="960" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:342,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:40631,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DYkR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png 424w, https://substackcdn.com/image/fetch/$s_!DYkR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png 848w, https://substackcdn.com/image/fetch/$s_!DYkR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png 1272w, https://substackcdn.com/image/fetch/$s_!DYkR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f78428b-930f-4731-a83b-ec91f9b79bd6_960x342.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Et comme vous pouvez le voir la syntaxe est loooongue...<br>Mais maintenant j&#8217;ai &#231;a :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VITv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VITv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png 424w, https://substackcdn.com/image/fetch/$s_!VITv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png 848w, https://substackcdn.com/image/fetch/$s_!VITv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png 1272w, https://substackcdn.com/image/fetch/$s_!VITv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VITv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png" width="1456" height="706" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:706,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:182085,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VITv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png 424w, https://substackcdn.com/image/fetch/$s_!VITv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png 848w, https://substackcdn.com/image/fetch/$s_!VITv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png 1272w, https://substackcdn.com/image/fetch/$s_!VITv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb308c0d3-bf7b-494f-a835-8e817bb5e0fb_2190x1062.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h3>Clear</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4u7f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4u7f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png 424w, https://substackcdn.com/image/fetch/$s_!4u7f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png 848w, https://substackcdn.com/image/fetch/$s_!4u7f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png 1272w, https://substackcdn.com/image/fetch/$s_!4u7f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4u7f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png" width="960" height="294" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:294,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:25760,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4u7f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png 424w, https://substackcdn.com/image/fetch/$s_!4u7f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png 848w, https://substackcdn.com/image/fetch/$s_!4u7f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png 1272w, https://substackcdn.com/image/fetch/$s_!4u7f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61da3d31-b74e-420d-9521-1b8d1f54f679_960x294.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Usage pr&#233;f&#233;r&#233; :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9Dqx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9Dqx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png 424w, https://substackcdn.com/image/fetch/$s_!9Dqx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png 848w, https://substackcdn.com/image/fetch/$s_!9Dqx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png 1272w, https://substackcdn.com/image/fetch/$s_!9Dqx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9Dqx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png" width="1438" height="198" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:198,&quot;width&quot;:1438,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31898,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9Dqx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png 424w, https://substackcdn.com/image/fetch/$s_!9Dqx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png 848w, https://substackcdn.com/image/fetch/$s_!9Dqx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png 1272w, https://substackcdn.com/image/fetch/$s_!9Dqx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1d80a7-fbb8-447e-b9ce-1b0dd19ae6fe_1438x198.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h3>tt et crtt</h3><p>Dans cette exemple je travaille sur un service `MonServiceSurLequelJeTravaille`, qui a besoin d&#8217;une instance de `Project` toute neuve. Tant que je travaille sur ce service, je vais passer ma vie &#224; &#233;crire ces trois lignes dans mon terminal :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9Ncy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9Ncy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png 424w, https://substackcdn.com/image/fetch/$s_!9Ncy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png 848w, https://substackcdn.com/image/fetch/$s_!9Ncy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png 1272w, https://substackcdn.com/image/fetch/$s_!9Ncy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9Ncy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png" width="1226" height="294" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:294,&quot;width&quot;:1226,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:62159,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9Ncy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png 424w, https://substackcdn.com/image/fetch/$s_!9Ncy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png 848w, https://substackcdn.com/image/fetch/$s_!9Ncy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png 1272w, https://substackcdn.com/image/fetch/$s_!9Ncy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F161ad51a-c6ed-4d9b-8056-9ce365654219_1226x294.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Afin de juste pouvoir faire un<code> key up </code>dans mon terminal pour relancer ma derni&#232;re commande, je pr&#233;f&#232;re &#233;crire ces trois lignes dans un fichier <code>test.rb</code> et dans mon terminal je ne fais qu&#8217;un <code>load(&#8217;test.rb&#8217;)</code></p><p>(comme j&#8217;utilise cette astuce dans tous mes projets rails, je l&#8217;ai rajout&#233; dans mon <a href="https://sebastiandedeyne.com/setting-up-a-global-gitignore-file/">gitignore global</a>)</p><p>J&#8217;imagine que vous trouverez aussi <code>load(&#8217;test.rb&#8217;)</code> beaucoup trop long &#224; &#233;crire, donc mon .irbrc s&#8217;est &#233;toff&#233; :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uHlF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uHlF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png 424w, https://substackcdn.com/image/fetch/$s_!uHlF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png 848w, https://substackcdn.com/image/fetch/$s_!uHlF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png 1272w, https://substackcdn.com/image/fetch/$s_!uHlF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uHlF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png" width="1456" height="292" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:292,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56358,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uHlF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png 424w, https://substackcdn.com/image/fetch/$s_!uHlF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png 848w, https://substackcdn.com/image/fetch/$s_!uHlF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png 1272w, https://substackcdn.com/image/fetch/$s_!uHlF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd77f2a0c-5ede-489f-9abf-578e321919e8_2184x438.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p><p>En r&#233;alit&#233; <code>tt</code> je ne l&#8217;utilise rarement car maintenant il y a la petite soeur :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oDTr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oDTr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png 424w, https://substackcdn.com/image/fetch/$s_!oDTr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png 848w, https://substackcdn.com/image/fetch/$s_!oDTr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png 1272w, https://substackcdn.com/image/fetch/$s_!oDTr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oDTr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png" width="960" height="486" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:486,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:28991,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oDTr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png 424w, https://substackcdn.com/image/fetch/$s_!oDTr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png 848w, https://substackcdn.com/image/fetch/$s_!oDTr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png 1272w, https://substackcdn.com/image/fetch/$s_!oDTr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58adea95-1247-43c6-8c00-fdac3d685fdd_960x486.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h3>lm</h3><p><strong>Mon probl&#232;me:</strong> Je passe ma vie &#224; oublier les noms de m&#233;thodes.</p><p>Par exemple:</p><ul><li><p>Je sais qu&#8217;il y a plein de m&#233;thodes qui jouent avec la case sur les strings.</p></li><li><p>Quand j&#8217;ajoute une colonne <code>firstname</code> &#224; mon user, il y a une bonne vingtaine de m&#233;thodes qui sont g&#233;n&#233;r&#233;es</p></li></ul><p>Mais dans un cas comme dans l&#8217;autre, je n&#8217;ai pas la pr&#233;tention (ni le d&#233;sir) de toutes les conna&#238;tre par c&#339;ur.</p><p>En ruby on peut faire cela :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q7Uh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png 424w, https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png 848w, https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png 1272w, https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png" width="1456" height="259" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:259,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51354,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png 424w, https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png 848w, https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png 1272w, https://substackcdn.com/image/fetch/$s_!Q7Uh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa69c40c0-0b62-4999-a9cb-7954179b1b3d_1650x294.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Beaucoup trop long! &#129326; Du coup j&#8217;ai rajout&#233; cela dans mon <code>.irbrc</code></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9N8N!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9N8N!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png 424w, https://substackcdn.com/image/fetch/$s_!9N8N!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png 848w, https://substackcdn.com/image/fetch/$s_!9N8N!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png 1272w, https://substackcdn.com/image/fetch/$s_!9N8N!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9N8N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png" width="1456" height="542" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/edbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:542,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:153082,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9N8N!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png 424w, https://substackcdn.com/image/fetch/$s_!9N8N!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png 848w, https://substackcdn.com/image/fetch/$s_!9N8N!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png 1272w, https://substackcdn.com/image/fetch/$s_!9N8N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedbef54f-53cf-44b1-95c3-5a1864c0e593_2210x822.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Et maintenant sur n&#8217;importe quel objet je peux faire .lm suivi par ce que je cherche. Donc si je reprends mon exemple :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JZk1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JZk1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png 424w, https://substackcdn.com/image/fetch/$s_!JZk1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png 848w, https://substackcdn.com/image/fetch/$s_!JZk1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png 1272w, https://substackcdn.com/image/fetch/$s_!JZk1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JZk1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png" width="960" height="246" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:246,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:23754,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JZk1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png 424w, https://substackcdn.com/image/fetch/$s_!JZk1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png 848w, https://substackcdn.com/image/fetch/$s_!JZk1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png 1272w, https://substackcdn.com/image/fetch/$s_!JZk1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F23762ba2-5d33-4994-9183-cfe2fb4e984b_960x246.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Et ensuite ?</h2><p>Moi j&#8217;utilise mon irb de cette fa&#231;on, mais ce fichier est avant tout l&#224; pour configurer votre irb. Vous avez la main sur le prompt, les couleurs et autre. Il y a beaucoup d&#8217;article dessus, et personnelement je n&#8217;ai jamais ressenti le besoin de toucher cette configuration.</p><p>Pour ma part, je m&#8217;en sers de deux fa&#231;ons:</p><p>- Soit pour des choses que je tape tr&#232;s souvent, et o&#249; les taper en 3 lettres au lieu de 20 me fait gagner beaucoup de temps.</p><p>- Soit des choses o&#249; je sais que la syntaxe est compliqu&#233;e, que je n&#8217;utilise pas tr&#232;s souvent, mais quand j&#8217;en ai besoin cela me fait gagner du temps de ne pas rechercher la syntaxe (ex: models &#9757;)</p><p>C&#8217;est donc &#224; vous de voir ce que vous faites souvent dans une console! &#128521;</p><div><hr></div><h2>Un dernier exemple</h2><p>Dans la derni&#232;re application que j&#8217;ai int&#233;gr&#233;, on a des variants (de produit) qui ont des codes-barres.</p><p>On passe notre vie &#224; les voir sur l&#8217;interface, mais leur id n&#8217;apparait jamais dans l&#8217;url, car ils sont toujours visible sur la page d&#8217;une autre ressource. Or c&#8217;est souvent notre point d&#8217;entr&#233;e pour les debugs.</p><p>J&#8217;ai donc eu un <strong>nouveau besoin</strong>: Pouvoir charg&#233; vite en m&#233;moire un variant depuis un code-barres :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!90YD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!90YD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png 424w, https://substackcdn.com/image/fetch/$s_!90YD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png 848w, https://substackcdn.com/image/fetch/$s_!90YD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png 1272w, https://substackcdn.com/image/fetch/$s_!90YD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!90YD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png" width="960" height="678" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e83aec83-749d-4ccd-950f-f19979c08248_960x678.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:678,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:61702,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/179342223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!90YD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png 424w, https://substackcdn.com/image/fetch/$s_!90YD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png 848w, https://substackcdn.com/image/fetch/$s_!90YD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png 1272w, https://substackcdn.com/image/fetch/$s_!90YD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe83aec83-749d-4ccd-950f-f19979c08248_960x678.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>&#9888; N&#8217;oubliez pas que lorsque vous surchargez des objets bas niveau, cela peut avoir des impacts. V&#233;rifiez bien que les noms de m&#233;thodes sont disponibles !</p><p>Et voil&#224; &#127881;</p><p><br>&#8212; <em><a href="https://www.linkedin.com/in/joseph-blanchard/overlay/about-this-profile/">Joseph</a></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🔒 On a ajouté le 2FA à notre app Rails en production, voilà exactement comment]]></title><description><![CDATA[Temps de lecture : 10 minutes]]></description><link>https://www.rubybiscuit.fr/p/implemente-un-2fa-two-factor-authentication</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/implemente-un-2fa-two-factor-authentication</guid><pubDate>Wed, 05 Nov 2025 15:01:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/849554bc-b8e4-40db-87c0-c8422cd176ac_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128680; Cette &#233;dition est relativement longue, votre bo&#238;te mail risque de la tronquer. Je vous conseille de cliquer sur le titre ci-dessus pour l&#8217;ouvrir dans votre navigateur web. &#9757;&#65039;</em></p><p>Hello les petits Biscuits !</p><p>Bienvenue sur la <strong>39&#232;me &#233;dition</strong> de Ruby Biscuit.<br>Vous &#234;tes maintenant <strong>600 abonn&#233;s</strong> &#129395;</p><p>Bonne lecture !</p><div><hr></div><p>Bonjour &#224; tous ! Aujourd&#8217;hui nous allons voir comment impl&#233;menter une feature de plus en plus populaire et demand&#233;e pour am&#233;liorer la s&#233;curit&#233; de nos comptes, le 2FA. C&#8217;est parti &#128071;&#127995;</p><p><strong>Mise en place</strong></p><p>Nous allons avoir besoin de <a href="https://github.com/devise-two-factor/devise-two-factor">devise-two-factor</a> (&#224; supposer que vous ayez <code>devise</code> sur votre plateforme bien entendu).</p><p>La version la plus r&#233;cente de devise-two-factor (6.1.0 &#224; l&#8217;heure o&#249; j&#8217;&#233;cris ces lignes) n&#8217;est pas destin&#233;e aux versions de Rails qui pr&#233;c&#232;dent la 7.0. Nous allons voir ici l&#8217;impl&#233;mentation avec une version de Rails plus ancienne, la 6.1.x. Pour cela nous allons devoir nous tourner vers la <a href="https://github.com/devise-two-factor/devise-two-factor/tree/v4.x">v4.x</a>.</p><p>Nous verrons en fin d&#8217;article ce qu&#8217;il faut mettre en place pour les versions de Rails plus r&#233;centes.</p><p><strong>Impl&#233;mentation</strong></p><ul><li><p>D&#233;terminer sur quel mod&#232;le mettre en place le 2FA. Nous utiliserons la table <code>users</code>.</p></li><li><p>Ajouter une config <code>Devise</code> &#224; notre mod&#232;le <code>User</code></p></li><li><p>Lancer la migration pour ajouter les champs en ajoutant un <code>default: false</code> sur la colonne <code>otp_required_for_login</code> si vous voulez feature flaguer par utilisateur.</p></li><li><p>Ajouter la dur&#233;e de validit&#233; du code dans <code>devise.rb</code> :</p></li><li><p>Enfin plus globalement, suivre la doc &#233;tape par &#233;tape est recommand&#233;e puisque cette derni&#232;re est tr&#232;s bien faite &#128077;</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dw-J!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dw-J!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png 424w, https://substackcdn.com/image/fetch/$s_!dw-J!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png 848w, https://substackcdn.com/image/fetch/$s_!dw-J!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png 1272w, https://substackcdn.com/image/fetch/$s_!dw-J!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dw-J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png" width="1400" height="966" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:966,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:174132,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dw-J!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png 424w, https://substackcdn.com/image/fetch/$s_!dw-J!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png 848w, https://substackcdn.com/image/fetch/$s_!dw-J!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png 1272w, https://substackcdn.com/image/fetch/$s_!dw-J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58d7942b-7850-4e71-ae24-033c9ea443f7_1400x966.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Nous allons d&#233;sormais nous occuper de notre <code>SessionsController</code> et mettre en place nos conditions. On veut pouvoir require le two factor sous certaines conditions donc ajoutons cette logique avec 2 callbacks :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jqkm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jqkm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png 424w, https://substackcdn.com/image/fetch/$s_!jqkm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png 848w, https://substackcdn.com/image/fetch/$s_!jqkm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png 1272w, https://substackcdn.com/image/fetch/$s_!jqkm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jqkm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png" width="1438" height="918" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:918,&quot;width&quot;:1438,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:144931,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jqkm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png 424w, https://substackcdn.com/image/fetch/$s_!jqkm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png 848w, https://substackcdn.com/image/fetch/$s_!jqkm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png 1272w, https://substackcdn.com/image/fetch/$s_!jqkm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157f4b16-6dae-437b-8d3c-f0f7ff613c75_1438x918.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>NB : <em>si vous n&#8217;avez pas d&#8217;environnement de production cette configuration peut vous sembler inutile puisque vous souhaitez l&#8217;activer sans condition aucune</em>.<br><em>Le </em><code>require_no_authentication</code><em> nous permet d&#8217;&#233;viter un flash error de devise comme quoi nous sommes d&#233;j&#224; connect&#233; lors de l&#8217;authentification avec le 2FA.</em></p><p>Voil&#224; pour la configuration globale de la gem !</p><p>D&#233;sormais nous allons ajouter notre logique m&#233;tier et envoyer un mail avec un code de confirmation lorsque le 2FA est actif.</p><p><strong>1&#232;re &#233;tape :</strong></p><p>La variable d&#8217;environnement <code>2FA_ENCRYPTION_KEY</code> n&#8217;est pas l&#224; pour faire joli. La m&#233;thode <code>generate_otp_secret</code> de la gem sert &#224; g&#233;n&#233;rer un secret sur l&#8217;objet vis&#233;. En effet, cela va g&#233;n&#233;rer des valeurs dans les champs que nous avons ajout&#233; dans la migration. &#192; savoir entre autre dans :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iBvR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iBvR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png 424w, https://substackcdn.com/image/fetch/$s_!iBvR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png 848w, https://substackcdn.com/image/fetch/$s_!iBvR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png 1272w, https://substackcdn.com/image/fetch/$s_!iBvR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iBvR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png" width="960" height="294" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:294,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38060,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iBvR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png 424w, https://substackcdn.com/image/fetch/$s_!iBvR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png 848w, https://substackcdn.com/image/fetch/$s_!iBvR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png 1272w, https://substackcdn.com/image/fetch/$s_!iBvR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7a0d27d-59a9-4d40-8972-2db9e99bc6a2_960x294.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><code>otp_secret</code>, qui est trouvable dans la <a href="https://github.com/devise-two-factor/devise-two-factor/tree/v4.x?tab=readme-ov-file#enabling-two-factor-authentication">documentation</a>, est <strong>obligatoire</strong> pour faire marcher le 2FA. Si vous tentez de vous connecter avec le two factor sans que votre utilisateur ai d&#8217;<code>otp_secret</code> alors vous aurez une 500.</p><p>Je vous conseille donc d&#8217;ajouter la ligne suivante &#224; chaque cr&#233;ation de <code>User</code> sur votre plateforme :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sHk3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sHk3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png 424w, https://substackcdn.com/image/fetch/$s_!sHk3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png 848w, https://substackcdn.com/image/fetch/$s_!sHk3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png 1272w, https://substackcdn.com/image/fetch/$s_!sHk3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sHk3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png" width="1456" height="190" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26041,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sHk3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png 424w, https://substackcdn.com/image/fetch/$s_!sHk3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png 848w, https://substackcdn.com/image/fetch/$s_!sHk3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png 1272w, https://substackcdn.com/image/fetch/$s_!sHk3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a72849a-fd5d-47dd-9b97-b986e8e77f2d_1516x198.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Si ensuite vous souhaitez feature flaguer par <code>User</code> vous pouvez ajouter 2 m&#233;thodes dans le mod&#232;le et les utiliser comme bon vous semble :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rSVu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rSVu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png 424w, https://substackcdn.com/image/fetch/$s_!rSVu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png 848w, https://substackcdn.com/image/fetch/$s_!rSVu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png 1272w, https://substackcdn.com/image/fetch/$s_!rSVu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rSVu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png" width="960" height="582" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:582,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:70599,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rSVu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png 424w, https://substackcdn.com/image/fetch/$s_!rSVu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png 848w, https://substackcdn.com/image/fetch/$s_!rSVu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png 1272w, https://substackcdn.com/image/fetch/$s_!rSVu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d88937f-e619-4bd6-9db2-db26483c2f30_960x582.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>2&#232;me &#233;tape :</strong></p><p>Direction le <code>SessionsController</code> pour y ajouter du code :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!M9v4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!M9v4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png 424w, https://substackcdn.com/image/fetch/$s_!M9v4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png 848w, https://substackcdn.com/image/fetch/$s_!M9v4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png 1272w, https://substackcdn.com/image/fetch/$s_!M9v4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!M9v4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png" width="1456" height="3078" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3078,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:985426,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!M9v4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png 424w, https://substackcdn.com/image/fetch/$s_!M9v4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png 848w, https://substackcdn.com/image/fetch/$s_!M9v4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png 1272w, https://substackcdn.com/image/fetch/$s_!M9v4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faef618de-320c-488b-a356-4ac03d8aab49_2228x4710.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Nous avons beaucoup de choses &#224; d&#233;cortiquer et nous allons regarder chaque &#233;tape de <code>authenticate_with_two_factor</code> pour comprendre.</p><ul><li><p>Tout d&#8217;abord si notre resource est vide nous voulons render le formulaire avec un message d&#8217;erreur.</p></li><li><p>Ensuite nous allons v&#233;rifier si dans le cookie le param &#8220;remember me&#8221; (<code>remember_2fa_id</code>) est pr&#233;sent. Vous savez c&#8217;est la petite checkbox qui ressemble &#224; &#231;a habituellement &#128071;</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!auHY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!auHY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg 424w, https://substackcdn.com/image/fetch/$s_!auHY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg 848w, https://substackcdn.com/image/fetch/$s_!auHY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!auHY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!auHY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg" width="693" height="607" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:607,&quot;width&quot;:693,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:83150,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!auHY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg 424w, https://substackcdn.com/image/fetch/$s_!auHY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg 848w, https://substackcdn.com/image/fetch/$s_!auHY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!auHY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c146eff-d79c-46c1-85cf-476d9836cbc4_693x607.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Auquel cas si le mdp est correct, on ne redemandera pas de passer par une deuxi&#232;me &#233;tape. Nous reviendrons sur cette histoire de cookie un peu plus tard.</p><ul><li><p>Sinon nous allons regarder si le param <code>otp_attempt</code> est pr&#233;sent. Il faut savoir que pour le 2FA nous avons 3 params obligatoires, l&#8217;<em>email</em>, le <em>mdp</em> et <em>otp_attempt</em>.<br>Nous allons &#233;galement checker si un param de session (<code>otp_user_id</code>) est pr&#233;sent (qu&#8217;on set par la suite), auquel cas nous consommons le token <code>validate_and_consume_otp!</code>, effa&#231;ons le param de session, enregistrons <code>remember_2fa_id</code> dans le cookie si l&#8217;utilisateur a coch&#233; la case et enfin l&#8217;authentifions.<br><br>NB : <em>la m&#233;thode </em><code>validate_and_consume_otp!</code><em> est trouvable dans la <a href="https://github.com/devise-two-factor/devise-two-factor/blob/cee1c83963332f6ce457a906dd7394f8d4fea971/lib/devise_two_factor/models/two_factor_authenticatable.rb#L36">documentation</a>.</em></p></li><li><p>Derni&#232;re &#233;tape, cr&#233;ons un token de session qui nous servira pour le mail puis envoyons ce dernier. N&#233;anmoins nous y ajoutons un lock pour ne pouvoir g&#233;n&#233;rer qu&#8217;un mail avec code unique par tranche horaire. C&#8217;est de la logique m&#233;tier et non obligatoire, vous pouvez vous en passer.</p></li><li><p>Si aucunes des conditions pr&#233;alables n&#8217;ont &#233;t&#233; r&#233;unies cela signifie que le mdp est incorrect et nous affichons un message d&#8217;erreur.</p></li></ul><p>Avec cela nous avons termin&#233; la premi&#232;re &#233;tape du 2FA. &#201;tape suivante, si je dois me connecter et rentrer un code de connexion alors j&#8217;ai besoin que l&#8217;on m&#8217;affiche un formulaire !</p><p><strong>3&#232;me &#233;tape :</strong></p><p>Cr&#233;er un fichier HTML (ici nous rentrons dans <code>&#8220;users/sessions/two_factor&#8221;</code>) pour y int&#233;grer le formulaire. Libre &#224; vous de choisir comment le pr&#233;senter, mais cela peut ressembler &#224; cela :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N3bx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N3bx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N3bx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N3bx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N3bx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N3bx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg" width="693" height="607" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:607,&quot;width&quot;:693,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74965,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N3bx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N3bx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N3bx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N3bx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0ec5636-24d7-4477-bc46-54f1eb17e55c_693x607.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Une fois le mail re&#231;u ainsi que le code qu&#8217;il contient (il vous faudra afficher <code>current_otp</code> &#224; votre utilisateur) nous allons rentrer ce code et repasser dans le <code>SessionController</code> puisque le formulaire est une requ&#234;te POST sur ce dernier.<br>Tout le chemin du <code>authenticate_with_two_factor</code> va donc &#234;tre r&#233;p&#233;t&#233; et vous pouvez d&#233;sormais comprendre l&#8217;int&#233;r&#234;t de <code>remember_2fa_id</code>. Si ce dernier &#233;tait pr&#233;sent &#224; la premi&#232;re &#233;tape alors nous ne serions pas pass&#233;s par ce formulaire et le controller nous aurait redirig&#233;.</p><p>Nous remarquons ici n&#233;anmoins le bouton &#8220;Renvoyer le code&#8221; et nous allons comprendre pourquoi nous avons mis en cache le <code>otp_token</code> :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VJ_g!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VJ_g!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png 424w, https://substackcdn.com/image/fetch/$s_!VJ_g!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png 848w, https://substackcdn.com/image/fetch/$s_!VJ_g!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png 1272w, https://substackcdn.com/image/fetch/$s_!VJ_g!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VJ_g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png" width="1208" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:342,&quot;width&quot;:1208,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46892,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VJ_g!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png 424w, https://substackcdn.com/image/fetch/$s_!VJ_g!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png 848w, https://substackcdn.com/image/fetch/$s_!VJ_g!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png 1272w, https://substackcdn.com/image/fetch/$s_!VJ_g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F501e93b2-7e63-4505-8415-691ee616e3d8_1208x342.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>C&#8217;est ici qu&#8217;intervient notre controller d&#8217;envoi de mail.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FTVU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FTVU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png 424w, https://substackcdn.com/image/fetch/$s_!FTVU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png 848w, https://substackcdn.com/image/fetch/$s_!FTVU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png 1272w, https://substackcdn.com/image/fetch/$s_!FTVU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FTVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png" width="1380" height="2214" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2214,&quot;width&quot;:1380,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:385559,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FTVU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png 424w, https://substackcdn.com/image/fetch/$s_!FTVU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png 848w, https://substackcdn.com/image/fetch/$s_!FTVU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png 1272w, https://substackcdn.com/image/fetch/$s_!FTVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdccfa0f-f102-4fb1-a536-b8eff6ed2058_1380x2214.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Pour &#233;viter d&#8217;envoyer le mail &#224; une mauvaise adresse nous allons v&#233;rifier que la valeur en cache est &#233;gale au param pass&#233; au controller, si tel est le cas on ajoute un lock (pour &#233;viter le spam) et on envoie le mail, sinon on redirige.</p><p>Pour une meilleure exp&#233;rience utilisateur, g&#233;rer cet envoi en AJAX est recommand&#233; &#128077;</p><p><strong>Tests</strong></p><p>Pour les tests il y a beaucoup de cas &#224; tester mais les plus globaux sont ceux-ci pour le <code>SessionsController</code> (n&#8217;h&#233;sitez pas &#224; tester tous les cas) :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ogl4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ogl4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png 424w, https://substackcdn.com/image/fetch/$s_!Ogl4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png 848w, https://substackcdn.com/image/fetch/$s_!Ogl4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png 1272w, https://substackcdn.com/image/fetch/$s_!Ogl4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ogl4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png" width="1456" height="2817" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2817,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:807437,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ogl4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png 424w, https://substackcdn.com/image/fetch/$s_!Ogl4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png 848w, https://substackcdn.com/image/fetch/$s_!Ogl4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png 1272w, https://substackcdn.com/image/fetch/$s_!Ogl4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4915739d-dc81-4151-8545-008244a4e50f_1690x3270.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Et pour le <code>SendOtpPasswordEmailsController</code> :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m2_6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m2_6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png 424w, https://substackcdn.com/image/fetch/$s_!m2_6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png 848w, https://substackcdn.com/image/fetch/$s_!m2_6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png 1272w, https://substackcdn.com/image/fetch/$s_!m2_6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m2_6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png" width="1456" height="2471" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2471,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:969434,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!m2_6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png 424w, https://substackcdn.com/image/fetch/$s_!m2_6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png 848w, https://substackcdn.com/image/fetch/$s_!m2_6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png 1272w, https://substackcdn.com/image/fetch/$s_!m2_6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70b3fc84-d8d3-4b42-89c5-6efafba5ff4a_2210x3750.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>En conclusion</strong></p><p>Vous avez maintenant toutes les cartes en main pour mettre en oeuvre votre 2FA au sign in sur votre app ! Reste &#224; d&#233;cider, selon vos besoins et logiques m&#233;tier, comment vous souhaitez organiser tout cela.<br>Pensez bien &#224; tester tous les cas de figures que vos utilisateurs pourraient avoir envie d&#8217;essayer. Notre <code>otp_user_id</code> par exemple sert, entre autre, &#224; conserver le bon id pour v&#233;rifier que si &#224; l&#8217;&#233;tape du 2FA je fais un retour arri&#232;re et qu&#8217;ensuite je rentre une autre adresse mail (car je peux avoir 2 comptes diff&#233;rents), alors le mail s&#8217;envoie &#224; la nouvelle adresse mail et non pas &#224; l&#8217;ancienne !</p><p>Merci pour votre lecture et j&#8217;esp&#232;re que l&#8217;article vous a plu &#128640; <br></p><p>Bonus : ce qu&#8217;il faut faire pour Rails 7+</p><ul><li><p>Ajouter d&#8217;autres variables d&#8217;environnement</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!j8E8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!j8E8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png 424w, https://substackcdn.com/image/fetch/$s_!j8E8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png 848w, https://substackcdn.com/image/fetch/$s_!j8E8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png 1272w, https://substackcdn.com/image/fetch/$s_!j8E8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!j8E8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png" width="1456" height="203" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:203,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:94653,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/176920775?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!j8E8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png 424w, https://substackcdn.com/image/fetch/$s_!j8E8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png 848w, https://substackcdn.com/image/fetch/$s_!j8E8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png 1272w, https://substackcdn.com/image/fetch/$s_!j8E8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5940b5d2-eedb-4fe8-804e-0dd877cb1cf1_2112x294.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p></li></ul><ul><li><p>La migration est diff&#233;rente.</p></li><li><p>Retirer <code>database_authenticatable</code> de votre mod&#232;le puisque les 2 modules sont incompatibles. Cela se fait normalement tout seul avec la version 4.x.</p></li></ul><p>Mais pour plus de pr&#233;cisions je vous invite &#224; regarder <a href="https://github.com/devise-two-factor/devise-two-factor">la documentation</a>.</p><p></p><p>&#8212; <em>Stanislas</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🤖 Mesurer la pertinence de Claude Code pour coder nos plateformes Ruby on Rails]]></title><description><![CDATA[Temps de lecture : 5 minutes]]></description><link>https://www.rubybiscuit.fr/p/mesurer-la-pertinence-de-claude-code</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/mesurer-la-pertinence-de-claude-code</guid><dc:creator><![CDATA[Mélanie]]></dc:creator><pubDate>Wed, 01 Oct 2025 14:03:14 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b2d097e4-fbd3-405a-8b9c-7c98e3984508_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la 38&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 598 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hCx8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hCx8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hCx8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70338978-608c-46b5-a844-572b314201ef_1456x816.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46940,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/101477020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hCx8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><p>Je suis d&#233;veloppeur back-end chez Capsens, et dans le cadre d&#8217;une &#233;valuation de la concurrence en mati&#232;re d&#8217;IA, on m&#8217;a donn&#233; une semaine pour tester le plan Max de Claude Code. Mon objectif principal &#233;tait de mesurer l&#8217;utilit&#233; r&#233;elle de cet outil dans notre contexte de d&#233;veloppement, d&#8217;analyser ses performances et de d&#233;terminer s&#8217;il offrait des gains de temps notables. Ce test visait &#233;galement &#224; identifier ses forces et ses limitations, afin de mieux comprendre ce qu&#8217;il pourrait apporter &#224; notre mani&#232;re de concevoir des plateformes, en comparaison avec les outils que nous utilisons habituellement.</p><p></p><h4>Disclaimer</h4><p>Il me semble important de pr&#233;ciser d&#8217;embl&#233;e le contexte de ce test : nous n&#8217;avons pas consacr&#233; un temps consid&#233;rable &#224; l&#8217;optimisation du fichier de configuration <code>CLAUDE.md</code>, et nous utilisons cet outil pour d&#233;velopper des plateformes relativement complexes. Mon retour d&#8217;exp&#233;rience ne pr&#233;tend donc pas refl&#233;ter le potentiel maximal de l&#8217;outil dans un environnement parfaitement configur&#233;. Notre chercheuse en IA m&#8217;a &#233;galement apport&#233; son aide sur certains aspects, ce qui a probablement influenc&#233; positivement certains r&#233;sultats. Ce retour refl&#232;te avant tout mon exp&#233;rience personnelle dans ces conditions sp&#233;cifiques d&#8217;utilisation.</p><p></p><h3>Mes impressions</h3><p>&#192; mon sens, Claude Code est un outil jeune qui n&#233;cessite une adaptation par rapport &#224; l&#8217;&#233;criture de code traditionnelle.</p><p>De prime abord, j&#8217;ai trouv&#233; l&#8217;exp&#233;rience un peu d&#233;routante, car il m&#8217;a fallu fournir des instructions extr&#234;mement pr&#233;cises et bien structur&#233;es pour obtenir un r&#233;sultat satisfaisant. En effet, j&#8217;ai constat&#233; que l&#8217;outil semble moins tol&#233;rant aux impr&#233;cisions ou aux formulations vagues, ce qui m&#8217;a oblig&#233; &#224; d&#233;crire les t&#226;ches de mani&#232;re minutieuse et exhaustive. Ce besoin de pr&#233;cision m&#8217;a parfois donn&#233; l&#8217;impression de ralentir mon processus de d&#233;veloppement, car je passais plus de temps &#224; affiner les instructions qu&#8217;&#224; &#233;crire du code, ce qui m&#8217;a sembl&#233; &#234;tre un contretemps en termes d&#8217;efficacit&#233;.</p><p>Dans mon exp&#233;rience, l&#8217;outil a pr&#233;sent&#233; des comportements parfois impr&#233;visibles, tant dans le code g&#233;n&#233;r&#233; que dans la gestion des d&#233;pendances. Parfois, il installait des d&#233;pendances sans aucune demande pr&#233;alable, ce qui m&#8217;a surpris. J&#8217;ai &#233;galement constat&#233; qu&#8217;il n&#8217;h&#233;sitait pas &#224; supprimer du code que je jugeais utile si cela semblait l&#8217;arranger. Cela m&#8217;a amen&#233; &#224; &#234;tre particuli&#232;rement vigilant concernant les solutions propos&#233;es, car j&#8217;ai appris qu&#8217;il &#233;tait indispensable de toujours v&#233;rifier ce qui avait &#233;t&#233; modifi&#233; ou ajout&#233; avant de l&#8217;int&#233;grer.</p><p>J&#8217;ai trouv&#233; l&#8217;exp&#233;rience utilisateur de Claude Code franchement mauvaise. J&#8217;ai constamment eu l&#8217;impression de faire de la relecture de code d&#8217;un d&#233;veloppeur junior. De plus, l&#8217;int&#233;gration avec le terminal m&#8217;a sembl&#233; usante et r&#233;barbative. Cependant, l&#8217;int&#233;gration avec VS Code a l&#233;g&#232;rement am&#233;lior&#233; mon exp&#233;rience en facilitant la gestion du code et en offrant une interface plus famili&#232;re. Cela m&#8217;a notamment permis une meilleure navigation et une prise en charge plus fluide des suggestions de code. Toutefois, j&#8217;ai trouv&#233; que cette int&#233;gration restait limit&#233;e et ne compensait pas enti&#232;rement les lacunes de l&#8217;outil, notamment en termes de r&#233;activit&#233;.</p><p></p><h3>Points positifs</h3><p><strong>1. Bonne compr&#233;hension du contexte back-end</strong></p><p>D&#8217;apr&#232;s mon exp&#233;rience, Claude Code d&#233;montre une bonne capacit&#233; &#224; comprendre le contexte global back-end de l&#8217;application. J&#8217;ai remarqu&#233; qu&#8217;il comprend bien comment Ruby on Rails fonctionne et qu&#8217;il arrive donc facilement &#224; naviguer &#224; travers l&#8217;application pour faire les modifications. N&#233;anmoins, bien que Claude Code soit g&#233;n&#233;ralement capable de naviguer efficacement dans la partie back-end, j&#8217;ai observ&#233; que son comportement peut parfois &#234;tre moins pr&#233;visible. Il m&#8217;est arriv&#233; qu&#8217;il n&#233;cessite un peu plus de contexte ou d&#8217;ajustements pour comprendre pleinement certaines sp&#233;cificit&#233;s du projet. En ce qui concerne le front, j&#8217;ai constat&#233; que l&#8217;outil a encore des limitations dans sa capacit&#233; &#224; naviguer de mani&#232;re autonome dans les fichiers n&#233;cessaires. Cela dit, je pense qu&#8217;il est possible que ces limitations puissent &#234;tre att&#233;nu&#233;es avec un param&#233;trage plus fin du fichier de configuration <code>CLAUDE.md</code> .</p><p><strong>2. Potentiel pour l&#8217;automatisation</strong></p><p>J&#8217;ai identifi&#233; que l&#8217;outil montre un fort potentiel pour :</p><ul><li><p>La cr&#233;ation de tests unitaires avec une couverture de 100%</p></li><li><p>La g&#233;n&#233;ration de tests d&#8217;int&#233;gration</p></li><li><p>La cr&#233;ation de donn&#233;es compl&#232;tes de seed pour les bases de donn&#233;es</p></li></ul><p><strong>3. Potentiel pour les traductions</strong></p><p>&#192; mes yeux, Claude Code est tr&#232;s bon dans la gestion des traductions. J&#8217;ai constat&#233; qu&#8217;il ma&#238;trise bien la syntaxe YAML et propose des traductions relativement pertinentes. En configurant le fichier <code>CLAUDE.md</code> , j&#8217;ai pu automatiser l&#8217;ensemble du processus : remplacement automatique des textes en dur par des appels <code>I18n.t()</code> , g&#233;n&#233;ration des cl&#233;s selon les conventions de nommage, et cr&#233;ation des fichiers de locale. Cette automatisation a consid&#233;rablement r&#233;duit le risque d&#8217;oublis lors de l&#8217;ajout de nouvelles fonctionnalit&#233;s.</p><p></p><h3>Points n&#233;gatifs</h3><p><strong>1. Processus chronophage</strong></p><p>J&#8217;ai observ&#233; que :</p><ul><li><p>La g&#233;n&#233;ration de code peut &#234;tre longue et fastidieuse (autour de 20 s par r&#233;ponse)</p></li><li><p>Plus le contexte est important, plus le temps de g&#233;n&#233;ration augmente</p></li><li><p>L&#8217;outil n&#233;cessite des instructions tr&#232;s d&#233;taill&#233;es, ce qui r&#233;duit selon moi le gain de temps de d&#233;veloppement</p></li></ul><p><strong>2. Comportements non ma&#238;tris&#233;s</strong></p><p>J&#8217;ai remarqu&#233; une tendance &#224; :</p><ul><li><p>Installer des d&#233;pendances non demand&#233;es</p></li><li><p>N&#233;cessiter une vigilance constante de ma part sur les actions automatis&#233;es</p></li><li><p>Introduire des bugs n&#233;cessitant des corrections</p></li><li><p>Prendre en compte le <code>CLAUDE.md </code>de mani&#232;re al&#233;atoire</p></li></ul><p>Exemple concret que j&#8217;ai rencontr&#233; : un test &#233;choue en raison de l&#8217;absence d&#8217;une gem. Plut&#244;t que de me proposer de l&#8217;installer, il tente de contourner le probl&#232;me. Je lui demande donc d&#8217;installer cette gem, ce qu&#8217;il fait, puis il relance les tests qui &#233;chouent &#224; nouveau, car il n&#8217;a fait qu&#8217;installer la gem sans l&#8217;int&#233;grer dans le fichier. Au lieu d&#8217;examiner comment utiliser la gem, il me propose &#224; nouveau la premi&#232;re solution de contournement.</p><p><strong>3. Efficacit&#233; questionn&#233;e</strong></p><p>De mon point de vue :</p><ul><li><p>La qualit&#233; du code n&#8217;est g&#233;n&#233;ralement pas meilleure en comparaison avec du code que je produis moi-m&#234;me</p></li><li><p>Le gain de temps n&#8217;est pas syst&#233;matique</p></li><li><p>Claude Code peut m&#234;me ralentir mon processus dans certains cas, par exemple :</p><ul><li><p>Lorsqu&#8217;il g&#233;n&#232;re du code impr&#233;cis ou incomplet, n&#233;cessitant des corrections manuelles suppl&#233;mentaires</p></li><li><p>Lorsqu&#8217;il n&#233;cessite un param&#233;trage fin et complexe, comme dans le cas de la configuration des d&#233;pendances ou des fichiers I18n</p></li><li><p>Lorsqu&#8217;il faut lui fournir des instructions tr&#232;s d&#233;taill&#233;es, ce qui me prend plus de temps que d&#8217;&#233;crire directement le code moi-m&#234;me</p></li></ul></li></ul><p><strong>4. N&#233;cessit&#233; de v&#233;rifications constantes</strong></p><p>J&#8217;ai constat&#233; :</p><ul><li><p>L&#8217;importance cruciale de demander l&#8217;&#233;criture de tests pour l&#8217;auto-correction</p></li><li><p>Le besoin de superviser toutes les modifications apport&#233;es</p></li><li><p>Le risque d&#8217;erreurs n&#233;cessitant une r&#233;vision manuelle</p></li></ul><p>Exemple concret que j&#8217;ai v&#233;cu : j&#8217;avais un probl&#232;me de configuration du gestionnaire d&#8217;assets Sprocket. Plut&#244;t que d&#8217;essayer de corriger le probl&#232;me de mani&#232;re traditionnelle, il m&#8217;a propos&#233; de r&#233;&#233;crire directement Sprocket.</p><p><strong>5. Fatigue intellectuelle et risque de validation automatique</strong></p><p>J&#8217;ai exp&#233;riment&#233; une forme particuli&#232;re de fatigue cognitive avec l&#8217;utilisation prolong&#233;e de l&#8217;outil. Passer des heures &#224; relire et valider du code g&#233;n&#233;r&#233; automatiquement cr&#233;e chez moi une lassitude qui peut conduire &#224; des validations h&#226;tives. Je trouve cela particuli&#232;rement dangereux car cela me pousse &#224; accepter des solutions m&#233;diocres ou inappropri&#233;es simplement par &#233;puisement mental.</p><p></p><h3>Conclusion</h3><p>Selon mon exp&#233;rience, Claude Code est une technologie &#233;mergente avec un bon potentiel, mais qui n&#233;cessite encore de nombreuses am&#233;liorations. J&#8217;ai trouv&#233; que l&#8217;outil est particuli&#232;rement prometteur pour l&#8217;automatisation des t&#226;ches r&#233;p&#233;titives comme la cr&#233;ation de tests et la g&#233;n&#233;ration de code standardis&#233;.</p><p><strong>Mes recommandations :</strong></p><ol><li><p><strong>Utilisation cibl&#233;e :</strong> Je recommande de privil&#233;gier Claude Code pour la g&#233;n&#233;ration de tests, de seeds et de code r&#233;p&#233;titif plut&#244;t que pour le d&#233;veloppement principal.</p></li><li><p><strong>Exp&#233;rience utilisateur :</strong> D&#8217;apr&#232;s mon exp&#233;rience, il vaut mieux privil&#233;gier l&#8217;int&#233;gration VS Code qui, bien que limit&#233;e, offre une exp&#233;rience l&#233;g&#232;rement plus confortable que le terminal.</p></li><li><p><strong>Approche progressive :</strong> Je sugg&#232;re de commencer par des t&#226;ches simples et d&#8217;augmenter progressivement la complexit&#233;.</p></li><li><p><strong>Supervision active :</strong> Selon moi, il est essentiel de maintenir une vigilance constante sur le code g&#233;n&#233;r&#233; et de syst&#233;matiquement demander des tests. Je d&#233;conseille &#233;galement de g&#233;n&#233;raliser cet outil chez les d&#233;veloppeurs juniors.</p></li><li><p><strong>Vision strat&#233;gique :</strong> Je consid&#232;re Claude Code comme un assistant permettant d&#8217;explorer des solutions plus ambitieuses plut&#244;t qu&#8217;un simple acc&#233;l&#233;rateur de productivit&#233;.</p></li></ol><p>En conclusion, de mon point de vue, Claude Code n&#8217;est pas encore un remplacement du d&#233;veloppement traditionnel, mais plut&#244;t un compl&#233;ment qui, bien utilis&#233;, peut enrichir le processus de d&#233;veloppement et permettre d&#8217;atteindre des objectifs plus ambitieux.</p><p>J&#8217;ai le sentiment que l&#8217;utilisation excessive de cet outil risque de cr&#233;er une perte de la connaissance globale de la plateforme. Un d&#233;veloppeur qui ne comprend pas intimement son code devient selon moi d&#233;pendant de l&#8217;outil et perd sa capacit&#233; &#224; intervenir efficacement en cas de probl&#232;me complexe ou d&#8217;&#233;volution de l&#8217;application.</p><p>De plus, il me semble important de noter que le co&#251;t financier (abonnements, utilisation API) et &#233;cologique (consommation &#233;nerg&#233;tique des mod&#232;les IA) reste &#233;lev&#233; par rapport aux gains de productivit&#233; que j&#8217;ai pu constater.</p><p>Son adoption doit &#234;tre r&#233;fl&#233;chie et progressive, en gardant &#224; l&#8217;esprit que l&#8217;objectif n&#8217;est pas n&#233;cessairement de coder plus vite, mais de pouvoir aller plus loin dans les r&#233;alisations techniques.</p><p>&#8212; <em>J&#233;r&#244;me, d&#233;veloppeur back-end RoR chez Capsens</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🧬 Une brève introduction au typage en Ruby]]></title><description><![CDATA[Aujourd&#8217;hui c&#8217;est Mathieu, CTO de Syadem, qui prend la parole pour vous parler du typage en Ruby !]]></description><link>https://www.rubybiscuit.fr/p/une-breve-introduction-au-typage</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/une-breve-introduction-au-typage</guid><dc:creator><![CDATA[Mélanie]]></dc:creator><pubDate>Wed, 03 Sep 2025 14:03:14 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3f542b14-212d-4974-b240-da7b95ed8443_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Aujourd&#8217;hui c&#8217;est Mathieu, CTO de <a href="https://www.syadem.com/fr/">Syadem</a>, qui prend la parole pour vous parler du </em>typage en Ruby !</p><p><em>Temps de lecture :<strong> 6 minutes</strong></em></p><div><hr></div><p>Hello les petits Biscuits !</p><p>Bienvenue sur la 37&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 595 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hCx8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hCx8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hCx8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70338978-608c-46b5-a844-572b314201ef_1456x816.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46940,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/101477020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hCx8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!hCx8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70338978-608c-46b5-a844-572b314201ef_1456x816.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><h2>Une br&#232;ve introduction au typage en Ruby</h2><p>Ajouter des types &#224; un langage dynamique comme Ruby, mais pourquoi ?</p><p>Quand on y r&#233;fl&#233;chit, c'est une id&#233;e moins surprenante qu'il para&#238;t.<br>Apr&#232;s tout, c'est ce que propose Typescript, avec le succ&#232;s qu'on conna&#238;t aujourd'hui.<br>Saviez-vous d'ailleurs qu'une majorit&#233; de d&#233;veloppeurs utilise d&#233;sormais TypeScript plut&#244;t que JavaScript ? <a href="#user-content-fn-1"><sup>1</sup></a><br>La tendance est claire : l'ajout d'un syst&#232;me de types &#224; un langage dynamique suscite un engouement certain.</p><p>Apr&#232;s tout, qui ne voudrait pas d'un monde o&#249; <code>undefined method 'machin' for nil:NilClass</code> ne serait plus qu'un lointain souvenir ?</p><p>Notre objectif dans cet article n'est pas de vous donner un cours magistral sur les syst&#232;mes de types, mais plut&#244;t de faire un &#233;tat de l'art de ce que Ruby propose aujourd'hui.<br>Alors accrochez-vous : on entre sur un terrain exp&#233;rimental, parfois instable, mais plut&#244;t excitant pour l'avenir de Ruby !</p><div><hr></div><h2>Le r&#244;le central du duck typing</h2><p>Avant de rentrer dans le vif du sujet avec un exemple de code, il est important de rappeler un pilier de Ruby : le duck typing.</p><blockquote><p>If it walks like a duck and it quacks like a duck, then it must be a duck.</p></blockquote><p><em>&#171; Duck typing &#187;</em> : une technique qui consiste &#224; ne pas se soucier de la classe exacte d'un objet, du moment qu'il r&#233;pond aux m&#233;thodes attendues.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5AiV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5AiV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!5AiV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!5AiV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!5AiV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5AiV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3365253,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/101477020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5AiV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!5AiV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!5AiV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!5AiV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3d3f0bb-78a8-46c0-9595-01aed22b049e_1536x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Magritte n'aurait probablement pas appr&#233;ci&#233;.</em></p><p>Prenons un exemple.<br>En Ruby, <code>puts</code> n'exige pas que l'argument soit une <code>String</code>. Il suffit que l'objet r&#233;ponde &#224; la m&#233;thode <code>#to_s</code>. C'est du duck typing pur !</p><p>Ce n'est donc pas la classe qui compte, mais l'ensemble des m&#233;thodes auxquelles un objet r&#233;pond.<br>Autrement dit, son "interface".</p><p>Dans un code Ruby classique, ces interfaces sont implicites : on suppose que tel objet a telle m&#233;thode, mais rien ne l'indique clairement.<br>Cela fonctionne tr&#232;s bien... jusqu'au moment o&#249; un bug se glisse, parce qu'un objet ne r&#233;pond pas &#224; la m&#233;thode attendue.</p><p>Vous voyez d&#233;j&#224; o&#249; on va en venir : un syst&#232;me de types permet de formaliser ces attentes.<br>Il ne s'agit plus seulement de supposer qu'un objet a une m&#233;thode, mais bien de le d&#233;clarer de mani&#232;re explicite.</p><p>Avant de pouvoir formaliser nos interfaces, il nous faut un moyen de les d&#233;crire.</p><div><hr></div><h2>&#201;crire des types avec RBS</h2><p>Ruby 3.0 a introduit un syst&#232;me de signatures de types appel&#233; <strong>RBS</strong> (<em>Ruby Signature</em>).<br>RBS est un langage de description de types qui permet de d&#233;finir les types des classes, des modules et des m&#233;thodes Ruby.</p><p>Il permet d'&#233;crire des fichiers de signatures <code>.rbs</code> contenant les d&#233;finitions des classes, modules, m&#233;thodes, variables d'instance, constantes, etc., ainsi que leurs types.<br>L'id&#233;e est de <strong>s&#233;parer les annotations de type du code Ruby lui-m&#234;me</strong> : au lieu d'ajouter des annotations en ligne dans les fichiers <code>.rb</code>, on &#233;crit des fichiers <code>.rbs</code> &#224; c&#244;t&#233;.</p><p>Un fichier RBS ressemble &#224; du Ruby simplifi&#233;, o&#249; les corps de m&#233;thodes sont remplac&#233;s par des signatures de type. Par exemple, si on a la classe Ruby suivante :</p><pre><code><code>class Duck
  def quark
    puts "Quark"
  end
end</code></code></pre><p>On peut &#233;crire la signature correspondante dans un fichier duck.rbs :</p><pre><code><code>class Duck
  def quark: () -&gt; void
end</code></code></pre><p>Bien s&#251;r, qui dit syst&#232;me de types dit aussi v&#233;rification de types (<em>type checking</em>).<br>C'est ici qu'entre en jeu <code>steep</code>.</p><div><hr></div><h2>Un exemple concret avec Steep (et RBS)</h2><p><strong><a href="https://github.com/soutaro/steep?utm_source=chatgpt.com">Steep</a></strong> est un <em>v&#233;rificateur de types</em> (ou <em>type checker</em>), cr&#233;&#233; par <strong>Soutaro Matsumoto</strong>, fortement impliqu&#233; dans la communaut&#233; Ruby 3.<br>Contrairement &#224; Sorbet, Steep <strong>n'introduit aucune annotation dans le code Ruby</strong> : il s'appuie <strong>uniquement</strong> sur les fichiers de signature (RBS) pour analyser le code.</p><p><strong>Matz, le cr&#233;ateur de Ruby, en est ravi</strong> : son langage reste ainsi pr&#233;serv&#233; !</p><p>L'int&#233;gration de Steep dans un nouveau projet Ruby est <strong>relativement simple</strong>.<br>Steep permet &#233;galement de s'appuyer sur des <strong>d&#233;finitions de types maintenues par la communaut&#233;</strong>, comme celles de <a href="https://github.com/soutaro/steep/blob/master/guides/src/gem-rbs-collection/gem-rbs-collection.md?utm_source=chatgpt.com">gem_rbs_collection</a>.</p><pre><code><code>bundle add steep --group=development
bundle exec steep init</code></code></pre><p><strong>La commande </strong><code>steep init</code> g&#233;n&#232;re un fichier de configuration, le <code>Steepfile</code>, &#224; la racine du projet.<br>Dans ce <code>Steepfile</code>, on sp&#233;cifie :</p><ul><li><p>quels r&#233;pertoires de code analyser (g&#233;n&#233;ralement <code>lib/</code> ou <code>app/</code> pour un projet Rails) ;</p></li><li><p>o&#249; se trouvent les signatures (par convention, dans <code>sig/</code>) ;</p></li><li><p>quelles librairies standards ou gems inclure (Steep sait charger les RBS de la <em>stdlib</em> et de <a href="https://github.com/ruby/gem_rbs_collection?utm_source=chatgpt.com">gem_rbs_collection</a>, par exemple).</p></li></ul><p><strong>L'int&#233;gration de Steep dans un projet existant semble plus complexe</strong> : il faut en effet <strong>d&#233;finir les signatures pour tous les fichiers</strong> avant d'obtenir un syst&#232;me fonctionnel.<br>De plus, rien ne garantit la pr&#233;sence de types pour les gems utilis&#233;es dans ce projet m&#234;me si avec gem_rbs_collection, les principales gems (comme Rails, par exemple) sont bien typ&#233;es : <a href="https://github.com/ruby/gem_rbs_collection/tree/main/gems?utm_source=chatgpt.com">liste</a>.</p><p>Voici un exemple simplifi&#233; de Steepfile :</p><pre><code><code>target :app do
  check 'app'
  signature 'sig'
  # je vous recommande cette option qui permet de relever toutes les incoh&#233;rences de type
  configure_code_diagnostics(Steep::Diagnostic::Ruby.all_error)
end</code></code></pre><p>Et le code d'une petite app :</p><pre><code><code>class Fish
  def swim
    "Le poisson nage dans l'eau."
  end
end

class Duck
  def speak
    "coin coin"
  end
end

class Cat
  def speak
    "meow"
  end
end

class SpeakService
  def initialize(animals)
    @animals = animals
  end

  def call
    animal = @animals.sample
    puts "The #{animal.class} says: #{animal.speak}"
  end
end

animals = [Duck.new, Cat.new] # le poisson n'est pas dans la liste des animaux

SpeakService.new(animals).call</code></code></pre><p>Cette application fonctionne parfaitement gr&#226;ce au duck typing !</p><p>Mais voil&#224;, des ann&#233;es plus tard, on demande une nouvelle fonctionnalit&#233; : ajouter le poisson dans la liste des animaux.<br>La classe <code>Fish</code> &#233;tait d&#233;j&#224; pr&#233;sente dans la codebase depuis longtemps, et elle est probablement utilis&#233;e ailleurs dans le code.<br>Naturellement, le d&#233;veloppeur charg&#233; d&#8217;impl&#233;menter cette fonctionnalit&#233; ne pensera pas forc&#233;ment &#224; v&#233;rifier si <code>Fish</code> poss&#232;de bien toutes les m&#233;thodes n&#233;cessaires.</p><pre><code><code># ...

animals = [Duck.new, Cat.new, Fish.new]

SpeakService.new(animals).call</code></code></pre><p>Et l&#224;, c'est la catastrophe : l'app plante al&#233;atoirement en production parce que le poisson n'a pas de m&#233;thode <code>speak</code>.<br>C'est pr&#233;cis&#233;ment dans ce genre de situation que le typage statique peut nous sauver la mise ! En d&#233;finissant des <strong>contrats clairs</strong> pour nos objets, on s'assure qu'ils r&#233;pondent bien aux m&#233;thodes attendues &#8212; et on &#233;vite ainsi ce type de probl&#232;me en production.</p><p>On aurait pu d&#233;finir les types ainsi d&#232;s le d&#233;but :</p><pre><code><code># sig/duck.rbs
class Duck
  include _Speakable
end

# sig/cat.rbs
class Cat
  include _Speakable
end

# sig/fish.rbs
class Fish
  def swim: () -&gt; String
end

# sig/speakable.rbs
interface _Speakable
  def speak: () -&gt; String
end
type speakable_object = _Speakable &amp; Object # une astuce pour que les speakables r&#233;pondent &#233;galement &#224; #class

# sig/speak_service.rbs
class SpeakService
  @animals: Array[speakable_object]

  def initialize: (Array[speakable_object] animals) -&gt; void
  def call: () -&gt; void
end</code></code></pre><p>Note : Il est possible de g&#233;n&#233;rer des types inf&#233;r&#233;s (avec <code>rbs prototype</code>), par exemple pour g&#233;n&#233;rer la signature de la classe <code>Fish</code>.<br><code>rbs prototype rb lib/fish.rb &gt; sig/fish.rbs</code></p><p>On lance la commande <code>steep check</code> (ou mieux, la CI s'en occupe) :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4YXQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4YXQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png 424w, https://substackcdn.com/image/fetch/$s_!4YXQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png 848w, https://substackcdn.com/image/fetch/$s_!4YXQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!4YXQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4YXQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png" width="1456" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:289620,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/101477020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4YXQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png 424w, https://substackcdn.com/image/fetch/$s_!4YXQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png 848w, https://substackcdn.com/image/fetch/$s_!4YXQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!4YXQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37bb4844-e582-4316-b4dc-6112fc0b3c33_2048x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ce n'est pas tr&#232;s clair, mais on comprend &#224; peu pr&#232;s que Fish ne satisfait pas l'interface _Speakable.<br>On ajuste donc le type.</p><pre><code><code>class Fish
  include _Speakable
  def swim: () -&gt; String
end</code></code></pre><p>On peut v&#233;rifier que le type est correct en lan&#231;ant <code>steep check</code> &#224; nouveau.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8_qX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8_qX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png 424w, https://substackcdn.com/image/fetch/$s_!8_qX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png 848w, https://substackcdn.com/image/fetch/$s_!8_qX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png 1272w, https://substackcdn.com/image/fetch/$s_!8_qX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8_qX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png" width="1456" height="753" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:753,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:181372,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/101477020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8_qX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png 424w, https://substackcdn.com/image/fetch/$s_!8_qX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png 848w, https://substackcdn.com/image/fetch/$s_!8_qX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png 1272w, https://substackcdn.com/image/fetch/$s_!8_qX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a76ec6d-21cd-4765-8f9c-ad4f97199cc4_1582x818.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Super, les types sont bons ! Mais il reste une erreur sur l'impl&#233;mentation de la class Fish.<br>Corrigeons cela en ajoutant une m&#233;thode <code>speak</code> &#224; la class Fish.</p><pre><code><code>class Fish
  def swim
    "The fish swims gracefully."
  end

  def speak
    "Blub blub"
  end
end</code></code></pre><p>Et pour se convaincre que tout marche bien : <code>steep check</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DoOj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DoOj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png 424w, https://substackcdn.com/image/fetch/$s_!DoOj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png 848w, https://substackcdn.com/image/fetch/$s_!DoOj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png 1272w, https://substackcdn.com/image/fetch/$s_!DoOj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DoOj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png" width="732" height="596" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:596,&quot;width&quot;:732,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:119194,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/101477020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DoOj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png 424w, https://substackcdn.com/image/fetch/$s_!DoOj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png 848w, https://substackcdn.com/image/fetch/$s_!DoOj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png 1272w, https://substackcdn.com/image/fetch/$s_!DoOj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7557953e-dec2-4e4e-b967-42c6097f01eb_732x596.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Plut&#244;t fastidieux, tous ces allers-retours entre la console et le code.<br>Heureusement, il est possible d'acc&#233;der &#224; ces erreurs <strong>directement depuis VSCode</strong>, gr&#226;ce aux extensions <code>steep</code> et <code>ruby-lsp</code>.</p><p>Cerise sur le g&#226;teau, on peut &#233;galement sauter &#224; la d&#233;finition des types.<br>Je vous recommande de le configurer dans VS Code, sinon la navigation entre les fichiers est un peu contraignante.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sGwP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sGwP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png 424w, https://substackcdn.com/image/fetch/$s_!sGwP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png 848w, https://substackcdn.com/image/fetch/$s_!sGwP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png 1272w, https://substackcdn.com/image/fetch/$s_!sGwP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sGwP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png" width="1438" height="1028" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1028,&quot;width&quot;:1438,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:138962,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/101477020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sGwP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png 424w, https://substackcdn.com/image/fetch/$s_!sGwP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png 848w, https://substackcdn.com/image/fetch/$s_!sGwP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png 1272w, https://substackcdn.com/image/fetch/$s_!sGwP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c0b3175-0de1-4e9f-9db8-ef9c5c81d43e_1438x1028.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p>Encore une derni&#232;re astuce pour les utilisateurs de Copilot : il faut reconna&#238;tre qu'il est franchement aux fraises concernant RBS.<br>Le langage est probablement trop r&#233;cent et les mod&#232;les manquent sans doute d'exemples.</p><p>Cependant, il suffit d'ajouter un petit lien vers la doc de la syntaxe RBS dans votre <code>.copilot-instructions.md</code>, et il devient un expert !</p><p>Le lien : <a href="https://github.com/ruby/rbs/blob/master/docs/syntax.md?utm_source=chatgpt.com">https://github.com/ruby/rbs/blob/master/docs/syntax.md</a></p><div><hr></div><h2>Pour conclure</h2><p>Le potentiel de <strong>Steep</strong> et <strong>RBS</strong> pour l&#8217;&#233;cosyst&#232;me Ruby est consid&#233;rable.<br>Ces outils permettent aux d&#233;veloppeurs de d&#233;tecter et corriger des erreurs potentielles bien avant la production, renfor&#231;ant ainsi la <strong>qualit&#233;</strong> et la <strong>robustesse</strong> de leurs applications.</p><p>Cependant, Steep n&#8217;est pas encore totalement pr&#234;t pour une utilisation &#224; grande &#233;chelle :</p><ul><li><p>La documentation reste incompl&#232;te</p></li><li><p>Il est difficile de l&#8217;adopter progressivement dans un projet existant</p></li><li><p>Toutes les fonctionnalit&#233;s de Ruby ne sont pas encore support&#233;es</p></li><li><p>RBS est encore jeune et susceptible d'&#233;voluer</p></li></ul><p>Malgr&#233; ces limites, je recommande vivement de l&#8217;exp&#233;rimenter sur un petit projet : l&#8217;usage combin&#233; de Steep et RBS est tr&#232;s prometteur.<br>L&#8217;int&#233;gration avec <strong>ruby-lsp</strong> fonctionne remarquablement bien et rend l&#8217;exp&#233;rience particuli&#232;rement agr&#233;able.</p><p>Pour la premi&#232;re fois en 15 ans de d&#233;veloppement Ruby, j&#8217;ai &#233;crit une API compl&#232;te sans aucun probl&#232;me de typage.<br>Cette API Rack de prise de rendez-vous (plusieurs centaines de lignes de code) a &#233;t&#233; d&#233;velopp&#233;e avec :</p><ul><li><p>des tests unitaires et d&#8217;int&#233;gration avec <code>rspec</code></p></li><li><p>du typage au runtime avec <code>dry-struct</code></p></li><li><p>et, &#233;videmment, du typage statique avec <code>steep</code></p></li></ul><p>Cette application, qui n&#8217;avait jamais &#233;t&#233; ex&#233;cut&#233;e en environnement local, a &#233;t&#233; d&#233;ploy&#233;e dans notre environnement de <em>staging</em> sans la moindre erreur.<br>Il n&#8217;y a eu aucun grain de sable dans les rouages : toutes ses composantes se sont int&#233;gr&#233;es sans probl&#232;me &#127881;.</p><p>Chez <strong><a href="https://www.syadem.com?utm_source=chatgpt.com">Syadem</a></strong>, nous utilisons beaucoup l&#8217;injection de d&#233;pendances.<br>Or, il est facile de mal injecter ou d&#8217;oublier une d&#233;pendance.<br>Cette fois, Steep m&#8217;a rattrap&#233; d&#232;s le d&#233;veloppement, &#233;vitant ainsi ces erreurs particuli&#232;rement frustrantes.</p><ol><li><p><a href="https://2024.stateofjs.com/en-US/usage/?utm_source=chatgpt.com#js_ts_balance">https://2024.stateofjs.com/en-US/usage/#js_ts_balance</a> <a href="#user-content-fnref-1">&#8617;</a></p></li></ol><p>&#8212; <em>Mathieu, CTO de <a href="https://www.syadem.com/fr/">Syadem</a></em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪 🖼️ Les vues SQL qui nous sauvent la mise.]]></title><description><![CDATA[Temps de lecture : 6 minutes]]></description><link>https://www.rubybiscuit.fr/p/vues-sql-les-bonnes-pratiques-et</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/vues-sql-les-bonnes-pratiques-et</guid><pubDate>Wed, 06 Aug 2025 14:02:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ac764c41-4107-4e4b-8900-1b79b42a438d_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la 36&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 591 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JKKg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JKKg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!JKKg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!JKKg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!JKKg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JKKg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:47298,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/169462882?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JKKg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!JKKg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!JKKg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!JKKg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60f18bdf-6b5e-4716-88e1-18b4dfdfe652_1456x816.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><p>Chez Capsens, j&#8217;ai r&#233;cemment d&#251; cr&#233;er un dashboard affichant des donn&#233;es agr&#233;g&#233;es provenant de plusieurs tables : nombre de contributions, montants collect&#233;s, statut des projets, etc. La requ&#234;te SQL sous-jacente &#233;tait assez lourde et utilis&#233;e &#224; plusieurs endroits de l&#8217;application. J&#8217;en ai donc profit&#233; pour tester les <strong>vues SQL</strong>, une solution qui permet d&#8217;am&#233;liorer significativement les performances de la requ&#234;te.<br><br>Une <strong>vue SQL</strong>, c&#8217;est un peu comme une &#171; fausse table &#187; : elle ne stocke pas directement des donn&#233;es, mais m&#233;morise une requ&#234;te SQL complexe pour qu&#8217;on puisse l&#8217;utiliser comme si c&#8217;&#233;tait une table classique. Super pratique pour &#233;viter les r&#233;p&#233;titions et simplifier la logique m&#233;tier dans vos requ&#234;tes !<br><br>Dans cet article, je vous propose de d&#233;couvrir ces fameuses vues SQL, comment les utiliser (notamment avec la gem <a href="https://github.com/scenic-views/scenic">Scenic</a>) et quelles sont leurs limites.</p><h3>Les deux types de vues SQL :</h3><p>Il existe deux types de vues SQL : les vues non mat&#233;rialis&#233;es (classiques) et les vues mat&#233;rialis&#233;es. Voyons la diff&#233;rence :</p><h3>1. Les vues non mat&#233;rialis&#233;es (classiques)</h3><p>Ce sont juste des requ&#234;tes sauvegard&#233;es qui sont recalcul&#233;es chaque fois que vous y acc&#232;dez.</p><p><strong>Avantages :</strong></p><ul><li><p>Simplifient ton code Ruby en &#233;vitant les requ&#234;tes complexes r&#233;p&#233;titives.</p></li><li><p>Centralisent la logique SQL.</p></li></ul><p><strong>Inconv&#233;nients :</strong></p><ul><li><p>Peuvent &#234;tre lentes car recalcul&#233;es &#224; chaque consultation.</p></li></ul><h3>Quand choisir une vue non mat&#233;rialis&#233;e ? &#128678;</h3><p>Vous pouvez envisager une vue non mat&#233;rialis&#233;e quand :</p><ul><li><p>Les requ&#234;tes SQL sont mod&#233;r&#233;ment complexes et fr&#233;quemment utilis&#233;es.</p></li><li><p>Les donn&#233;es doivent toujours &#234;tre &#224; jour en temps r&#233;el (<em>par exemple, si vous affichez un stock de produits en temps r&#233;el dans un dashboard).</em></p></li><li><p>Vous voulez centraliser la logique SQL afin de garder un code plus clair, coh&#233;rent et maintenable.</p></li></ul><h4>Exemple pratique avec Scenic, l&#8217;ami des vues SQL dans Rails &#128665;</h4><p>Scenic est une gem Ruby qui simplifie consid&#233;rablement la gestion des views SQL dans Rails, qu'elles soient mat&#233;rialis&#233;es ou non. Elle permet une int&#233;gration propre et lisible dans ton application Rails, avec une gestion native des migrations.</p><p>Cr&#233;er une vue non mat&#233;rialis&#233;e :</p><pre><code><code>rails generate scenic:view active_users</code></code></pre><p>Cette commande g&#233;n&#232;re automatiquement :</p><ul><li><p>Un fichier SQL : <code>db/views/active_users_v01.sql</code></p></li><li><p>Une migration pour cr&#233;er ta vue</p></li></ul><p>Exemple du fichier SQL :</p><pre><code><code>SELECT users.*</code>
<code>FROM users</code>
<code>WHERE users.active = true</code>
<code>ORDER BY users.created_at DESC;</code></code></pre><p>Migration Rails g&#233;n&#233;r&#233;e :</p><pre><code><code>class CreateActiveUsers &lt; ActiveRecord::Migration[7.0]</code>
<code>  def change</code>
<code>    create_view :active_users</code>
<code>  end</code>
<code>end</code></code></pre><p>On peut m&#234;me associer un model &#224; cette vue ! Et donc y stocker nos m&#233;thodes, scopes et m&#234;me les associations !</p><pre><code><code>class ActiveUser &lt; ApplicationRecord</code>
<code>  self.table_name = 'active_users'</code>

<code>  scope :recent, -&gt; { order(created_at: :desc) }</code>
<code>end</code></code></pre><h3>2. Les vues mat&#233;rialis&#233;es</h3><p>Elles sauvegardent les r&#233;sultats physiquement dans la base de donn&#233;es et doivent &#234;tre rafra&#238;chies r&#233;guli&#232;rement ou sur demande.</p><p><strong>Avantages :</strong></p><ul><li><p>Beaucoup plus rapides, surtout pour les requ&#234;tes lourdes.</p></li><li><p>R&#233;duisent la charge sur la base de donn&#233;es.</p></li></ul><p><strong>Inconv&#233;nients :</strong></p><ul><li><p>Prennent plus d&#8217;espace de stockage.</p></li><li><p>Peuvent afficher des donn&#233;es l&#233;g&#232;rement p&#233;rim&#233;es si elles ne sont pas rafra&#238;chies fr&#233;quemment.</p></li></ul><h3>Quand choisir une vue mat&#233;rialis&#233;e ? &#128678;</h3><p>Vous pouvez envisager une vue mat&#233;rialis&#233;e quand :</p><ul><li><p>Vous avez as des requ&#234;tes tr&#232;s co&#251;teuses que vous utilisez souvent (rapports, statistiques, etc.).</p></li><li><p>Les donn&#233;es n&#8217;ont pas besoin d'&#234;tre mises &#224; jour en temps r&#233;el.</p></li></ul><h4>Exemple pratique (toujours avec Scenic &#128665;) :</h4><p>Cr&#233;er une vue mat&#233;rialis&#233;e :</p><pre><code><code>rails generate scenic:view latest_orders --materialized</code></code></pre><p>Cette commande g&#233;n&#232;re automatiquement :</p><ul><li><p>Un fichier SQL : <code>db/views/latest_orders_v01.sql</code></p></li><li><p>Une migration pour cr&#233;er ta vue</p></li></ul><p>Exemple du fichier SQL :</p><pre><code><code>SELECT orders.*, users.email AS user_email</code>
<code>FROM orders</code>
<code>JOIN users ON users.id = orders.user_id</code>
<code>ORDER BY orders.created_at DESC</code>
<code>LIMIT 100;</code></code></pre><p>Migration Rails g&#233;n&#233;r&#233;e :</p><pre><code><code>class CreateLatestOrders &lt; ActiveRecord::Migration[7.0]</code>
<code>  def change</code>
<code>    create_view :latest_orders, materialized: true</code>
<code>  end</code>
<code>end</code></code></pre><p>Mod&#232;le associ&#233; &#224; cette vue :</p><pre><code><code>class LatestOrder &lt; ApplicationRecord</code>
<code>  self.primary_key = :id</code>
<code>  self.table_name = 'latest_orders'</code>

<code>  belongs_to :user, foreign_key: :user_id</code>

<code>  scope :recent, -&gt; { order(created_at: :desc) }</code>

<code>  def self.refresh</code>
<code>    Scenic.database.refresh_materialized_view(table_name, concurrently: true)</code>
<code>  end</code>
<code>end</code></code></pre><p>Important : si vous voulez rafra&#238;chir la vue sans bloquer ton application (<code>concurrently: true</code>), il faut imp&#233;rativement ajouter un index unique :</p><pre><code><code>class AddIndexToLatestOrders &lt; ActiveRecord::Migration[7.0]</code>
<code>  def change</code>
<code>    add_index :latest_orders, :id, unique: true</code>
<code>  end</code>
<code>end</code></code></pre><p><strong>Attention !</strong> L&#8217;option <code>concurrently: true</code> exige PostgreSQL &#8805; 9.4 et un index unique.</p><h3>Versionner une vue SQL avec Scenic &#128260;</h3><p>Si vous souhaitez faire &#233;voluer ta vue existante, Scenic permet facilement de g&#233;rer diff&#233;rentes versions :</p><pre><code><code>rails generate scenic:view latest_orders --materialized --replace</code></code></pre><p>Cette commande g&#233;n&#232;re une nouvelle version du fichier SQL (<code>latest_orders_v02.sql</code>) ainsi qu'une migration adapt&#233;e.</p><h3>Bonus : Tester tes vues SQL &#129514;</h3><p>Voici comment vous pouvez tester facilement votre vue SQL avec RSpec par exemple :</p><pre><code><code># spec/models/latest_order_spec.rb</code>
<code>require 'rails_helper'</code>

<code>RSpec.describe LatestOrder, type: :model do</code>
<code>  describe '.recent' do</code>
<code>    subject { described_class.recent }</code>

<code>    let(:user) { create(:user) }</code>
<code>    let!(:order1) { create(:order, user: user, created_at: 1.day.ago) }</code>
<code>    let!(:order2) { create(:order, user: user, created_at: 1.hour.ago) }</code>

<code>    it 'returns orders ordered by created_at: :desc' do</code>
<code>      described_class.refresh # obligatoire pour une vue mat&#233;rialis&#233;e</code>
<code>      expect(subject.pluck(:id)).to eq(</code>
<code>        [order2.id, order1.id]</code>
<code>      )</code>
<code>    end</code>
<code>  end</code>
<code>end</code></code></pre><p>Voil&#224; ! Vous connaissez maintenant les bonnes pratiques des vues SQL avec Rails, comment les versionner et m&#234;me comment les tester facilement. &#192; vous de jouer avec Scenic ! &#128640;</p><p><br>&#8212; <em>Christopher</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪👯 10 000 inscriptions en 15 minutes : comment on a évité de faire planter notre CRM]]></title><description><![CDATA[Temps de lecture : 5 minutes]]></description><link>https://www.rubybiscuit.fr/p/maitrise-la-synchronisation-de-ton</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/maitrise-la-synchronisation-de-ton</guid><pubDate>Wed, 02 Jul 2025 14:02:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b143e9d4-8d0b-4ce4-b312-27027239f13c_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Temps de lecture :<strong> 5 minutes</strong></em></p><div><hr></div><p>Hello les petits Biscuits !</p><p>Bienvenue sur la 35&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 593 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-nwp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-nwp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!-nwp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!-nwp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!-nwp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-nwp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:47742,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/167346305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-nwp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!-nwp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!-nwp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!-nwp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b6c62fe-7f0a-4e17-990d-a31d11edf1b0_1456x816.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Bonne lecture !</p><div><hr></div><p>Chez Capsens, nous sommes r&#233;guli&#232;rement amen&#233; &#224; synchroniser des CRM : Hubspot, Salesforce, PipeDrive, Zoho, Brevo, Airtable, etc.</p><p>Tous ces CRM impl&#233;mentent un syst&#232;me de rate limit pour prot&#233;ger leur infrastructure d'un trop grand nombre de requ&#234;tes. Souvent ce dernier se situe aux alentours de 10 requ&#234;tes par seconde <em>(Brevo, Hubspot, Airtable).</em></p><p>Lorsqu'il est n&#233;cessaire que la synchronisation soit faite en se basant sur des &#233;v&#233;nements, il faut penser &#224; g&#233;rer le rate limit pour s'assurer de ne pas le d&#233;passer m&#234;me en cas de pic de charge.</p><blockquote><p>No no Jos&#233; ! rescue l'erreur 429 ("Too Many Requests") et retry plus tard n'est pas une solution suffisante.</p></blockquote><h2>Cr&#233;ation du job</h2><p>Dans la mesure du possible, nous essayons de toujours prioriser l'utilisation de Sidekiq lorsqu'il s'agit d'une API. Pour cet article, nous allons prendre l'exemple d'un CRM quelconque avec lequel nous avons besoin de synchroniser les utilisateurs. La synchronisation devra se faire suite &#224; des &#233;v&#233;nements particuliers :</p><ul><li><p>inscription</p></li><li><p>investissement sign&#233;</p></li><li><p>investissement pay&#233;</p></li><li><p>investissement annul&#233;</p></li><li><p>...</p></li></ul><p>Nous allons donc cr&#233;er un job Sidekiq idempotent qui prendra en param&#232;tre l'ID de l'un de nos utilisateurs en base de donn&#233;es et s'occupera de mettre &#224; jour le contact dans le CRM s'il existe ou de le cr&#233;er si ce n'est pas le cas avec une liste de champs d&#233;finie que nous appellerons "dictionnaire".</p><p>Afin d'&#233;conomiser des requ&#234;tes au CRM, nous allons impl&#233;menter une sorte de cl&#233; d'idempotence qui va nous permettre de ne pas effectuer de requ&#234;te de MAJ ou cr&#233;ation du contact au CRM dans le cas o&#249; les informations que nous avons envoy&#233; la derni&#232;re fois sont identiques. Nous pourrions nous baser sur le timestamp de la resource &#224; synchroniser mais bien souvent les informations &#224; envoyer proviennent de mutliples tables en BDD et la gestion deviendrait bien trop complexe (m&#234;me en utilisant des <code>touch</code>).</p><p>Dans ce contexte, il est donc plus simple et fiable de g&#233;n&#233;rer le dictionnaire et cr&#233;er une signature unique &#224; partir de celui-ci : un <strong>digest</strong> avec la m&#233;thode <code>.hexdigest</code> fournie par <code>Digest::SHA256</code> et de stocker celle-ci sur l'utilisateur en cas de synchronisation afin de pouvoir la comparer lors du prochain passage.</p><blockquote><p><em>En informatique, une fonction de hashage ou digest est un r&#233;sum&#233; court et unique d&#8217;un ensemble de donn&#233;es, obtenu en appliquant une fonction de hachage (hash). Il sert souvent &#224; v&#233;rifier l&#8217;int&#233;grit&#233; ou l&#8217;authenticit&#233; des donn&#233;es, car toute modification du contenu d&#8217;origine modifie aussi le digest.</em></p></blockquote><p>Voici un exemple concret :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i-eX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i-eX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png 424w, https://substackcdn.com/image/fetch/$s_!i-eX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png 848w, https://substackcdn.com/image/fetch/$s_!i-eX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!i-eX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i-eX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png" width="1310" height="1264" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1264,&quot;width&quot;:1310,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:205892,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/167346305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!i-eX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png 424w, https://substackcdn.com/image/fetch/$s_!i-eX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png 848w, https://substackcdn.com/image/fetch/$s_!i-eX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!i-eX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe85481f7-cf29-4ad0-b6ab-e489ba6476c7_1310x1264.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Commen&#231;ons par cr&#233;er notre Service qui va encapsuler la logique :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sO_x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sO_x!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png 424w, https://substackcdn.com/image/fetch/$s_!sO_x!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png 848w, https://substackcdn.com/image/fetch/$s_!sO_x!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png 1272w, https://substackcdn.com/image/fetch/$s_!sO_x!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sO_x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png" width="1456" height="2123" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/738710b9-0708-4282-a664-d40653b425e6_1830x2668.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2123,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:536681,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/167346305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sO_x!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png 424w, https://substackcdn.com/image/fetch/$s_!sO_x!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png 848w, https://substackcdn.com/image/fetch/$s_!sO_x!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png 1272w, https://substackcdn.com/image/fetch/$s_!sO_x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F738710b9-0708-4282-a664-d40653b425e6_1830x2668.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Pour le besoin de l'article, nous avons cr&#233;er un PORO (Pure Old Ruby Object) tr&#232;s simple afin de ne pas avoir de d&#233;pendances &#224; des libs mais chez Capsens nous utilisons normalement des DryMonads.</p><p>Cr&#233;ons le fichier de tests associ&#233; :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7ZRn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7ZRn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png 424w, https://substackcdn.com/image/fetch/$s_!7ZRn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png 848w, https://substackcdn.com/image/fetch/$s_!7ZRn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png 1272w, https://substackcdn.com/image/fetch/$s_!7ZRn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7ZRn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png" width="1426" height="3676" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3676,&quot;width&quot;:1426,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:748304,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/167346305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7ZRn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png 424w, https://substackcdn.com/image/fetch/$s_!7ZRn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png 848w, https://substackcdn.com/image/fetch/$s_!7ZRn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png 1272w, https://substackcdn.com/image/fetch/$s_!7ZRn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F613668a1-3be7-4360-a75a-4c457e14ff03_1426x3676.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Il ne reste plus qu'&#224; cr&#233;er notre worker Sidekiq qui va appeler notre monad :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FYqW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FYqW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png 424w, https://substackcdn.com/image/fetch/$s_!FYqW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png 848w, https://substackcdn.com/image/fetch/$s_!FYqW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png 1272w, https://substackcdn.com/image/fetch/$s_!FYqW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FYqW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png" width="1454" height="1516" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1516,&quot;width&quot;:1454,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:291108,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/167346305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FYqW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png 424w, https://substackcdn.com/image/fetch/$s_!FYqW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png 848w, https://substackcdn.com/image/fetch/$s_!FYqW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png 1272w, https://substackcdn.com/image/fetch/$s_!FYqW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cc294e4-ec08-4b27-97fe-4173fa7e803a_1454x1516.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>et la suite de tests associ&#233;e :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8c26!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8c26!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png 424w, https://substackcdn.com/image/fetch/$s_!8c26!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png 848w, https://substackcdn.com/image/fetch/$s_!8c26!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png 1272w, https://substackcdn.com/image/fetch/$s_!8c26!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8c26!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png" width="1456" height="1457" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1457,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:509473,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/167346305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8c26!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png 424w, https://substackcdn.com/image/fetch/$s_!8c26!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png 848w, https://substackcdn.com/image/fetch/$s_!8c26!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png 1272w, https://substackcdn.com/image/fetch/$s_!8c26!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd25b1744-75eb-4007-9b6f-2d27c1b43d51_2018x2020.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Cependant la situation actuelle pose un probl&#232;me, imaginons que la plateforme subisse un pic de traffic accru d&#251; &#224; un passage t&#233;l&#233;vision, nous avons un &#233;v&#232;nement (entre autres) d&#233;clench&#233; par l'inscription d'un utilisateur. Si nous avons 10 000 utilisateur qui s'inscrivent sur une p&#233;riode de 15 minutes, cela ferait donc une moyenne de 11 inscrits par seconde, nous d&#233;passerions le rate limit de notre CRM.</p><h3>Utilisation de Sidekiq Throttled</h3><p>Dans ce genre de cas o&#249; l'on souhaite limiter la fr&#233;quence ou la concurrence d'une t&#226;che au cours du temps, <a href="https://github.com/ixti/sidekiq-throttled">Sidekiq Throttled</a> est g&#233;n&#233;ralement notre ami chez Capsens.</p><p>C'est une gem Ruby qui ajoute des m&#233;canismes de <strong>throttling</strong> (limitation) pour Sidekiq, permettant de contr&#244;ler simultan&#233;ment la <strong>concurrence</strong> des jobs et leur <strong>fr&#233;quence d&#8217;ex&#233;cution</strong> (rate&#8209;limiting).</p><p>Elle fournit une configuration simple via <code>sidekiq_throttle</code>, avec des strat&#233;gies telles que <code>concurrency</code> (nombre max de jobs concurrents) et <code>threshold</code> (nombre max de jobs dans une p&#233;riode donn&#233;e.</p><p>Cette derni&#232;re est assez simple &#224; configurer, il faut bien s&#251;r ajouter la gem dans le Gemfile puis modifier le fichier de config Sidekiq :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J7hx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J7hx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png 424w, https://substackcdn.com/image/fetch/$s_!J7hx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png 848w, https://substackcdn.com/image/fetch/$s_!J7hx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!J7hx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J7hx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png" width="1238" height="1048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1048,&quot;width&quot;:1238,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:186060,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/167346305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J7hx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png 424w, https://substackcdn.com/image/fetch/$s_!J7hx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png 848w, https://substackcdn.com/image/fetch/$s_!J7hx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!J7hx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5551674b-e968-43b0-b7fb-e73223c77713_1238x1048.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>  </p><p>Notez les lignes 18 &#224; 22 o&#249; l'on ajoute un <code>Sidekiq::Throttled::Registry</code> , ce dernier nous permet de configurer une limitation pour un ensemble de job plut&#244;t que pour un seul job. C'est tr&#232;s pratique puisqu'il est fort probable que nous impl&#233;mentions de nouveau job qui vont effectuer des requ&#234;tes &#224; notre CRM (supprimer un user, synchroniser d'autres ressources, d&#233;clencher des mails). De cette mani&#232;re, nous nous assurons que tous les jobs qui effectuent des requ&#234;tes soient limit&#233;s de la m&#234;me mani&#232;re.</p><p>Enfin, notez aussi que m&#234;me si le rate limit du CRM est de 10 requ&#234;tes par seconde, je prend de la marge en limitant &#224; 5 car d'autres parties prenantes effectuent probablement des requ&#234;tes au CRM ou parce qu'il est probable que certains de mes futurs jobs effectuent plus d'une requ&#234;te en s'ex&#233;cutant.</p><p>Maintenant que Sidekiq Throttled est configur&#233;, il ne reste plus qu'&#224; l'impl&#233;menter dans notre job :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oOn9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oOn9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png 424w, https://substackcdn.com/image/fetch/$s_!oOn9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png 848w, https://substackcdn.com/image/fetch/$s_!oOn9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png 1272w, https://substackcdn.com/image/fetch/$s_!oOn9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oOn9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png" width="1454" height="1624" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1624,&quot;width&quot;:1454,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:315732,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/167346305?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oOn9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png 424w, https://substackcdn.com/image/fetch/$s_!oOn9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png 848w, https://substackcdn.com/image/fetch/$s_!oOn9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png 1272w, https://substackcdn.com/image/fetch/$s_!oOn9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a5d580e-6597-4336-84fe-cb0df5df0e2b_1454x1624.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>D&#233;sormais vous n'avez plus besoin de vous inqui&#233;tez du rate limit, celui-ci est g&#233;r&#233; &#224; la fois par notre job mais surtout par la limitation fournie par Sidekiq Throttled. Vous pouvez m&#234;me impl&#233;menter une cron pour programmer la synchronisation d'utilisateur en batch sans vous inqui&#233;tez de quoi que ce soit. Gardez cependant en t&#234;te que cette impl&#233;mentation permet de pr&#233;server le CRM gr&#226;ce au digest et au throttling mais elle ne pr&#233;serve pas votre serveur qui lui va travailler dans tous les cas pour g&#233;n&#233;rer le digest et le comparer &#224; celui de l'utilisateur afin d'&#233;viter toute requ&#234;te superflue au CRM.</p><p>&#8212; <em>Isma&#235;l</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪 🤼 Active Storage Versus Shrine: le match]]></title><description><![CDATA[Temps de lecture : 7 minutes]]></description><link>https://www.rubybiscuit.fr/p/active-storage-versus-shrine-le-match</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/active-storage-versus-shrine-le-match</guid><pubDate>Wed, 04 Jun 2025 14:02:24 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7657d8c7-df0a-4a2a-acf9-1ebfa5a482dd_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la 34&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 585 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Pc9k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Pc9k!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!Pc9k!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!Pc9k!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!Pc9k!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Pc9k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48916,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/165182953?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Pc9k!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp 424w, https://substackcdn.com/image/fetch/$s_!Pc9k!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp 848w, https://substackcdn.com/image/fetch/$s_!Pc9k!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp 1272w, https://substackcdn.com/image/fetch/$s_!Pc9k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39e201c5-2662-48f9-91cf-68f055b5c700_1456x816.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture </p><div><hr></div><h2><strong>Active Storage Versus Shrine: le match</strong></h2><p>Ce mois-ci, nous abordons la gestion des fichiers dans Ruby on Rails, une fonctionnalit&#233; indispensable pour que vos utilisateurs puissent t&#233;l&#233;verser (uploader) des photos, des vid&#233;os ou des documents PDF. D&#233;couvrez deux gems populaires utilis&#233;es dans la communaut&#233;: <a href="https://guides.rubyonrails.org/active_storage_overview.html">ActiveStorage </a>et <a href="https://shrinerb.com/">Shrine</a>.</p><p></p><p><strong>Partie Th&#233;orique</strong></p><p>Avant de nous lancer, faisons un bref rappel sur ce que font ces gems :</p><ol><li><p>T&#233;l&#233;charger des fichiers depuis un navigateur vers un service de stockage de fichiers</p></li><li><p>Faire le lien entre ces fichiers et vos instances Active Record</p></li></ol><p>Que ce soit pour Shrine ou Active Storage, l'installation se fait tr&#232;s facilement. Pour les 2 gems, vous trouverez la plupart des r&#233;ponses &#224; vos questions dans la documentation et en particulier comment les installer.</p><p></p><div><hr></div><p><strong>TL;DR</strong></p><p>Les diff&#233;rences entre Shrine et Active Storage sont g&#233;n&#233;ralement mineures, mais votre choix d&#233;pendra surtout de votre usage :</p><ul><li><p>Si vous g&#233;rez principalement <strong>un seul fichier par mod&#232;le</strong>, Shrine offre une approche plus flexible et explicite</p></li><li><p>Si vous devez g&#233;rer <strong>plusieurs fichiers par mod&#232;le</strong> ou que vous souhaitez une solution pr&#234;te &#224; l&#8217;emploi et int&#233;gr&#233;e &#224; Rails, Active Storage sera souvent plus pratique.</p></li></ul><div><hr></div><p><strong>Base de donn&#233;es</strong></p><p>Pour commencer, je voudrais attaquer la comparaison par le prisme de la base de donn&#233;es. Quelles sont les diff&#233;rences d'architecture et qu'est-ce que &#231;a implique sur les migrations par exemple ?</p><p>Avec Shrine, c'est simple, chaque nouveau fichier n&#233;cessite une migration sur le mod&#232;le.</p><pre><code><code>class AddCoverPictureToProjects &lt; ActiveRecord::Migration</code>
<code>  def change</code>
<code>    add_column :projects, :cover_picture_data, :text</code>
<code>  end</code>
<code>end</code></code></pre><p>Ensuite on va mettre &#224; jour notre mod&#232;le</p><pre><code><code>class Project &lt; ApplicationRecord</code>
<code>  include ImageUploader::Attachment(:cover_picture)</code>
<code>end</code></code></pre><p>Pour Active Storage, c'est encore plus simple, on va cr&#233;er 2 tables au d&#233;but et puis c'est tout:</p><ul><li><p><code>active_storage_blobs</code></p></li><li><p><code>active_storage_attachments</code></p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cUFT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cUFT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png 424w, https://substackcdn.com/image/fetch/$s_!cUFT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png 848w, https://substackcdn.com/image/fetch/$s_!cUFT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png 1272w, https://substackcdn.com/image/fetch/$s_!cUFT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cUFT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png" width="753" height="301" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:301,&quot;width&quot;:753,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:40173,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/165182953?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cUFT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png 424w, https://substackcdn.com/image/fetch/$s_!cUFT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png 848w, https://substackcdn.com/image/fetch/$s_!cUFT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png 1272w, https://substackcdn.com/image/fetch/$s_!cUFT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62d38241-40c9-43a4-838e-eb2d45480d60_753x301.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">sch&#233;ma des tables active_storage</figcaption></figure></div><p>Ensuite, il suffira de mettre &#224; jour son mod&#232;le &#224; chaque fois qu'on veut lier un nouveau fichier &#224; un mod&#232;le.</p><pre><code><code>class Project &lt; ApplicationRecord</code>
<code>  has_one_attached :cover_picture</code>
<code>end</code></code></pre><p>Sous cette simplicit&#233; apparente se cache une r&#233;alit&#233; bien plus complexe. Par exemple, quand on appelle la photo dans la vue, voici ce que fait AST sous le capot :</p><ul><li><p>Il cherche dans la table <code>active_storage_attachments</code></p><ul><li><p>record_type: "Project"</p></li><li><p>record_id: 1</p></li><li><p>name : "cover_picture"</p></li></ul></li><li><p>Ensuite il va chercher dans la table <code>active_storage_blobs</code></p></li><li><p>Enfin il g&#233;n&#232;re l'URL</p></li></ul><p>Vous voyez venir le probl&#232;me. En appelant une image (ou un document) dans un index, on obtient non pas une <strong>N+1</strong> mais bel et bien une <strong>2N + 1</strong> ! Alors que sur Shrine, il n'y a aucune requ&#234;te SQL suppl&#233;mentaire.</p><p>Pour la petite histoire, j'ai d&#233;j&#224; travaill&#233; sur une page d'accueil avec 160 requ&#234;tes SQL pour aller r&#233;cup&#233;rer et afficher des images ! Un simple includes a r&#233;solu le probl&#232;me</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HRvH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HRvH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png 424w, https://substackcdn.com/image/fetch/$s_!HRvH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png 848w, https://substackcdn.com/image/fetch/$s_!HRvH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png 1272w, https://substackcdn.com/image/fetch/$s_!HRvH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HRvH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png" width="1064" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:675,&quot;width&quot;:1064,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:161668,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/165182953?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HRvH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png 424w, https://substackcdn.com/image/fetch/$s_!HRvH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png 848w, https://substackcdn.com/image/fetch/$s_!HRvH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png 1272w, https://substackcdn.com/image/fetch/$s_!HRvH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd378639f-444d-41ae-b96c-2884bbedc69a_1064x675.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">2 x 80 + 1 = 161 requ&#234;tes SQL !</figcaption></figure></div><p>Avec notre exemple d'aujourd'hui, et une syntaxe plus moderne, &#231;a donnerait &#231;a dans le controller.</p><pre><code><code>@projects = Project.with_attached_cover_picture</code></code></pre><p>Un des avantages avec l'architecture d'AST, c'est qu'on peut facilement ajouter plusieurs fichiers dans notre mod&#232;le. Par exemple pour ajouter des photos &#224; un projet :</p><pre><code><code>class Project &lt; ApplicationRecord</code>
<code>  has_many_attached :photos</code>
<code>end</code></code></pre><p>Alors que dans Shrine, &#231;a passe forc&#233;ment par cr&#233;er un nouveau mod&#232;le.</p><pre><code><code>class Photo &lt; ApplicationRecord</code>
<code>  belongs_to :project</code>
<code>  include ImageUploader::Attachment(:file)</code>
<code>end</code>

<code>class Project &lt; ApplicationRecord</code>
<code>  has_many :photos</code>
<code>end</code></code></pre><div><hr></div><p><strong>Les formulaires</strong></p><p>Quand un utilisateur a plusieurs photos &#224; ajouter sur un projet dans un formulaire, ce qui est pratique de pouvoir les s&#233;lectionner toutes ensemble.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hbR8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hbR8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png 424w, https://substackcdn.com/image/fetch/$s_!hbR8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png 848w, https://substackcdn.com/image/fetch/$s_!hbR8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png 1272w, https://substackcdn.com/image/fetch/$s_!hbR8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hbR8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png" width="656" height="388" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:388,&quot;width&quot;:656,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:105721,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/165182953?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hbR8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png 424w, https://substackcdn.com/image/fetch/$s_!hbR8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png 848w, https://substackcdn.com/image/fetch/$s_!hbR8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png 1272w, https://substackcdn.com/image/fetch/$s_!hbR8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49dc9b80-7d90-444b-bcf0-9824c7c28ff1_656x388.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Pour AST, il suffit simplement d'ajouter &#231;a &#224; votre formulaire dans la vue</p><pre><code><code>&lt;%= form.file_field :photos, multiple: true %&gt;</code>
</code></pre><p>Ensuite dans le controller</p><pre><code><code>class ProjectsController &lt; ApplicationController</code>
<code>  def create</code>
<code>    @project = Project.new(project_params)</code>
<code>    if @project.save</code>
<code>      redirect_to project_path(@project)</code>
<code>    else</code>
<code>      render :new</code>
<code>    end</code>
<code>  end</code>

<code>  private</code>

<code>  def project_params</code>
<code>    params.require(:project).permit(photos: [])</code>
<code>  end</code>
<code>end</code>
</code></pre><p>Ce fut ma plus grosse frustration en passant de AST &#224; Shrine que de ne pas pouvoir faire &#231;a aussi simplement .</p><p>Pour avoir l'&#233;quivalent, j'ai d&#251; rajouter quelques lignes de code dans le mod&#232;le</p><pre><code><code>class Photo &lt; ApplicationRecord</code>
<code>  belongs_to :project</code>
<code>  include ImageUploader::Attachment(:file)</code>
<code>end</code>

<code>class Project &lt; ApplicationRecord</code>
<code>  has_many :photos</code>
<code>  accepts_nested_attributes_for :photos</code>
<code>  attr_accessor :new_photos</code>

<code>  def new_photos=(files)</code>
<code>    files.each do |file|</code>
<code>      photos.build(file: file)</code>
<code>    end</code>
<code>  end</code>
<code>end</code></code></pre><p>Et maintenant dans ma vue je peux utiliser &#231;a</p><pre><code><code>&lt;%= f.file_field :new_photos, multiple: true %&gt;</code></code></pre><p>Dans le controller il faut juste mettre &#224; jour cette ligne</p><pre><code><code>params.require(:project).permit(new_photos: [])</code></code></pre><div><hr></div><p><strong>Mon plus gros bug (&#224; cause du polymorphisme)</strong></p><p>La flexibilit&#233; qu'am&#232;ne le <a href="https://blog.appsignal.com/2022/05/25/an-introduction-to-polymorphism-in-ruby-on-rails.html">polymorphisme</a> sur AST vient avec son lot de pi&#232;ge. &#199;a a caus&#233; une des plus grosses frayeurs de code.</p><p>Apr&#232;s avoir chang&#233; le nom d'un mod&#232;le pour une refacto, je pousse mon code en production, et l&#224; surprise, tous les fichiers avaient disparu !</p><p>L'explication peut paraitre simple &#224; posteriori mais sur le moment je n'y avais pas pens&#233;. Je vous explique : dans la table <code>active_storage_attachments</code> la colonne <code>record_type</code> qui stocke le nom du mod&#232;le doit &#234;tre mise &#224; jour en m&#234;me temps que le changement de nom du mod&#232;le.</p><p>Il n y'a aucune v&#233;rification, ni de la base de donn&#233;es, ni de l'application, que le record (combinaison record_id et record_type) existe bel et bien. C'est uniquement sur vos petites &#233;paules que reposent la concordance des noms. Alors que sur Shrine, il n'y a besoin de rien faire.</p><div><hr></div><p><strong>Tests</strong></p><p>Une autre erreur qui m'est arriv&#233; sur AST, c'est d'oublier de supprimer les fichiers apr&#232;s les tests. On se retrouve en local avec une taille projet qui augmente de mani&#232;re exponentielle et fait saturer le disque dur. En faisant une analyse rapide (avec la commande <code>$ du</code>), je me suis vite rendu compte que mon dossier tmp/storage faisait plusieurs giga octet.</p><p>Pourtant, c'est bien marqu&#233; dans la <a href="https://guides.rubyonrails.org/v7.2/active_storage_overview.html#system-tests">documentation</a> !!! (RTFM)</p><p>Voici la solution pour Rspec</p><pre><code><code>config.after(:suite) do</code>
<code>  path = "#{Rails.root}/tmp/storage"</code>
<code>  if Dir.exist?(path)</code>
<code>    FileUtils.remove_dir(path)</code>
<code>  end</code>
<code>end</code></code></pre><p>J'ai une petite pr&#233;f&#233;rence pour ce que fait Shrine qui permet de mettre les fichiers dans la RAM. C'est plus rapide et plus simple &#224; g&#233;rer &#128515;</p><pre><code><code>Shrine.storages[:store] = Shrine::Storage::Memory.new</code></code></pre><div><hr></div><p><strong>Les variantes (AST) ou d&#233;rivatives (Shrine)</strong></p><p>Ce sont des images modifi&#233;es &#224; une taille sp&#233;cifique pour avoir des images plus l&#233;g&#232;res ou qui s'ins&#232;rent facilement dans du code html. Les 2 utilisent <a href="https://github.com/rmagick/rmagick">image_magick</a> ou <a href="https://github.com/libvips/ruby-vips">vips</a>. Attention pour les 2 gems aussi, la g&#233;n&#233;ration des images se fait c&#244;t&#233; back-end ce qui peut consid&#233;rablement ralentir votre serveur. L'avantage de AST c'est que si vous d&#233;finissez vos variantes dans le mod&#232;le, elles sont cr&#233;&#233;es dans un background Job.</p><div><hr></div><p><strong>Conclusion : Comparaison n'est pas raison</strong></p><p>Comme souvent en informatique, quand on veut comparer, la r&#233;ponse va &#234;tre : "&#231;a d&#233;pend du contexte". Pour une application de logistique, qui doit r&#233;cup&#233;rer beaucoup de photos par commande pour v&#233;rifier le bon d&#233;roulement d'une livraison (mon pr&#233;c&#233;dent job), AST sera plus adapt&#233;. En revanche, pour une application qui a une image par projet et quelques PDF &#224; sauvegarder, Shrine convient tr&#232;s bien.</p><p>Il faut juste avoir conscience qu'avec AST, il faudra faire attention aux N+1 au risque de ralentir votre application. Sur Shrine, il est plus rare de voir des requ&#234;tes SQL incontroll&#233;es.</p><p>Enfin AST &#233;tant nativement dans Rails, il est plus rassurant sur le long-terme mais pas forc&#233;ment plus simple &#224; maintenir.</p><div><hr></div><p><em>&#8212; Alexandre</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🐳 Le Dockerfile Rails qu'on utilise vraiment en production chez Capsens]]></title><description><![CDATA[Temps de lecture : 5 minutes]]></description><link>https://www.rubybiscuit.fr/p/construire-un-dockerfile-rails-de</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/construire-un-dockerfile-rails-de</guid><pubDate>Wed, 07 May 2025 14:02:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f6fc06f5-a65e-46ed-a2ff-b5b660028c04_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128680; Cette &#233;dition est relativement longue car bcp d&#8217;image, votre bo&#238;te mail risque de la tronquer. Je vous conseille de cliquer sur le titre ci-dessus pour l&#8217;ouvrir dans votre navigateur web. &#9757;&#65039;</em></p><p>Hello les petits Biscuits !</p><p>Bienvenue sur la 33&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 583 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KKiv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KKiv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png 424w, https://substackcdn.com/image/fetch/$s_!KKiv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png 848w, https://substackcdn.com/image/fetch/$s_!KKiv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png 1272w, https://substackcdn.com/image/fetch/$s_!KKiv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KKiv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:573880,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KKiv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png 424w, https://substackcdn.com/image/fetch/$s_!KKiv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png 848w, https://substackcdn.com/image/fetch/$s_!KKiv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png 1272w, https://substackcdn.com/image/fetch/$s_!KKiv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a59ae11-b782-4bfe-8959-954cf7fe0406_2858x1602.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><h1>&#128640; Construire un Dockerfile Rails de production : mon approche DevOps</h1><p>Je l&#8217;avoue, je suis un peu maniaque des co&#251;ts et des temps de build : je d&#233;teste qu&#8217;une image devienne ob&#232;se, ou qu&#8217;un build s&#8217;&#233;ternise&#8230; surtout quand je pourrais d&#233;j&#224; boire un caf&#233; &#9749;&#65039; ! Pour tous mes projets Rails, j&#8217;ai mis au point un Dockerfile unique, rapide et l&#233;ger.</p><p>Dans cet article, je vous partage mes astuces (et mes petites manies &#128540;)&#8239;: comment j&#8217;en suis arriv&#233; l&#224;, et surtout <strong>pourquoi</strong> j&#8217;ai fait chaque choix.</p><div><hr></div><h2>&#128150; Pourquoi j&#8217;aime chouchouter mon Dockerfile Rails</h2><p>Le Dockerfile, c&#8217;est souvent le parent pauvre du projet, mais c&#8217;est le <strong>c&#339;ur</strong> de votre environnement d&#8217;ex&#233;cution. Le n&#233;gliger, c&#8217;est un peu comme oublier d&#8217;ajouter du sel dans une recette&#8239;: &#231;a peut vite tourner au d&#233;sastre&#8239;! Une mauvaise config peut entra&#238;ner&#8239;:</p><ul><li><p>&#128230; Des images trop lourdes (bonjour les t&#233;l&#233;chargements interminables)</p></li><li><p>&#128012; Des builds qui tra&#238;nent (adieu la productivit&#233;)</p></li><li><p>&#128275; Des secrets expos&#233;s (le cauchemar)</p></li><li><p>&#128680; Des failles de s&#233;curit&#233; (panique &#224; bord)</p></li></ul><p>J&#8217;ai donc d&#233;cid&#233; de rendre ce fichier maintenable, s&#233;curis&#233; et r&#233;utilisable sur tous mes projets.</p><div><hr></div><h2>&#128295; Un Dockerfile tout-terrain pour tous les projets</h2><p>Pour &#233;viter de dupliquer un Dockerfile par projet, j&#8217;ai introduit plusieurs <strong>ARG</strong> :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!43Cs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!43Cs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png 424w, https://substackcdn.com/image/fetch/$s_!43Cs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png 848w, https://substackcdn.com/image/fetch/$s_!43Cs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png 1272w, https://substackcdn.com/image/fetch/$s_!43Cs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!43Cs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png" width="1270" height="352" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:352,&quot;width&quot;:1270,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:108912,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!43Cs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png 424w, https://substackcdn.com/image/fetch/$s_!43Cs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png 848w, https://substackcdn.com/image/fetch/$s_!43Cs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png 1272w, https://substackcdn.com/image/fetch/$s_!43Cs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83ec0fb0-7c3c-4412-bb2e-5cada04844b2_1270x352.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Cette param&#233;trisation permet de :</p><ol><li><p>Choisir dynamiquement la version de Ruby et de Debian ;</p></li><li><p>Installer, si n&#233;cessaire, des paquets sp&#233;cifiques &#224; un projet ;</p></li><li><p>Adapter l&#8217;environnement Rails et Node sans modifier le Dockerfile.</p></li></ol><p>Par exemple, je peux lancer :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FLj1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FLj1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png 424w, https://substackcdn.com/image/fetch/$s_!FLj1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png 848w, https://substackcdn.com/image/fetch/$s_!FLj1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png 1272w, https://substackcdn.com/image/fetch/$s_!FLj1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FLj1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png" width="1268" height="314" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:314,&quot;width&quot;:1268,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:69582,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FLj1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png 424w, https://substackcdn.com/image/fetch/$s_!FLj1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png 848w, https://substackcdn.com/image/fetch/$s_!FLj1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png 1272w, https://substackcdn.com/image/fetch/$s_!FLj1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf53cc3b-9c0f-40f7-8d7f-a0845e5d4e47_1268x314.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>et obtenir une image taill&#233;e sur mesure.</p><div><hr></div><h2>&#127959;&#65039; Image de base et d&#233;pendances communes</h2><p>Dans l&#8217;&#233;tape <code>base</code>, j&#8217;installe les d&#233;pendances syst&#232;me partag&#233;es par tous les projets :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aqHO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aqHO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png 424w, https://substackcdn.com/image/fetch/$s_!aqHO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png 848w, https://substackcdn.com/image/fetch/$s_!aqHO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png 1272w, https://substackcdn.com/image/fetch/$s_!aqHO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aqHO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png" width="1258" height="522" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/be0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:522,&quot;width&quot;:1258,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:127273,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aqHO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png 424w, https://substackcdn.com/image/fetch/$s_!aqHO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png 848w, https://substackcdn.com/image/fetch/$s_!aqHO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png 1272w, https://substackcdn.com/image/fetch/$s_!aqHO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe0631e9-b3e5-404b-86fd-fbd23511f568_1258x522.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><strong>Pourquoi ces paquets ?</strong></p><ul><li><p><code>libjemalloc2</code> am&#233;liore la gestion m&#233;moire de Ruby ;</p></li><li><p><code>libpq5</code> est n&#233;cessaire au client PostgreSQL ;</p></li><li><p><code>shared-mime-info</code>, <code>file</code> et <code>less</code> sont utiles pour les scripts et diagnostics.</p></li></ul></blockquote><p>Gr&#226;ce aux <code>--mount=type=cache</code>, les index APT sont mis en cache, acc&#233;l&#233;rant les builds suivants.</p><div><hr></div><h2>Configuration Ruby et mise &#224; jour de Rubygems</h2><p>Avant tout build applicatif, je d&#233;finis des variables d&#8217;environnement et je mets &#224; jour Rubygems pour b&#233;n&#233;ficier des derni&#232;res am&#233;liorations :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eCSi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eCSi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png 424w, https://substackcdn.com/image/fetch/$s_!eCSi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png 848w, https://substackcdn.com/image/fetch/$s_!eCSi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png 1272w, https://substackcdn.com/image/fetch/$s_!eCSi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eCSi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png" width="1258" height="450" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:1258,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:85831,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eCSi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png 424w, https://substackcdn.com/image/fetch/$s_!eCSi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png 848w, https://substackcdn.com/image/fetch/$s_!eCSi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png 1272w, https://substackcdn.com/image/fetch/$s_!eCSi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46378f88-2877-4390-8f3e-c9c92fd5db29_1258x450.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><strong>Astuce :</strong> Pr&#233;voir toujours un <code>RAILS_ENV</code> explicite garantit des builds reproductibles.</p></blockquote><div><hr></div><h2>&#128736;&#65039; &#201;tape 1 : production-builder</h2><p>L&#8217;&#233;tape <code>production-builder</code> regroupe l&#8217;installation des outils et la compilation :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d6xL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d6xL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png 424w, https://substackcdn.com/image/fetch/$s_!d6xL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png 848w, https://substackcdn.com/image/fetch/$s_!d6xL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png 1272w, https://substackcdn.com/image/fetch/$s_!d6xL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d6xL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png" width="1260" height="134" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:134,&quot;width&quot;:1260,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22483,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!d6xL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png 424w, https://substackcdn.com/image/fetch/$s_!d6xL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png 848w, https://substackcdn.com/image/fetch/$s_!d6xL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png 1272w, https://substackcdn.com/image/fetch/$s_!d6xL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59afdd95-24e9-48f4-81fd-2b8126b7779b_1260x134.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h3>1. Outils de compilation</h3><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1Ukr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1Ukr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png 424w, https://substackcdn.com/image/fetch/$s_!1Ukr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png 848w, https://substackcdn.com/image/fetch/$s_!1Ukr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png 1272w, https://substackcdn.com/image/fetch/$s_!1Ukr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1Ukr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png" width="1260" height="312" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:312,&quot;width&quot;:1260,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:83169,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1Ukr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png 424w, https://substackcdn.com/image/fetch/$s_!1Ukr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png 848w, https://substackcdn.com/image/fetch/$s_!1Ukr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png 1272w, https://substackcdn.com/image/fetch/$s_!1Ukr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45123552-897e-41c4-9196-4adf1b25db5e_1260x312.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Ces paquets sont essentiels &#224; la compilation :</p><ul><li><p><code>build-essential</code> : compilateurs C/C++ pour les extensions Ruby ;</p></li><li><p><code>libssl-dev</code>, <code>libyaml-dev</code>, <code>libz-dev</code>&#8230; : d&#233;pendances fr&#233;quentes des gems natives ;</p></li><li><p><code>git</code> : pour les gems h&#233;berg&#233;es sur Git.</p></li></ul><h3>2. Node.js et Yarn</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!T_3d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!T_3d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png 424w, https://substackcdn.com/image/fetch/$s_!T_3d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png 848w, https://substackcdn.com/image/fetch/$s_!T_3d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png 1272w, https://substackcdn.com/image/fetch/$s_!T_3d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!T_3d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png" width="1264" height="420" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:420,&quot;width&quot;:1264,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120145,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!T_3d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png 424w, https://substackcdn.com/image/fetch/$s_!T_3d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png 848w, https://substackcdn.com/image/fetch/$s_!T_3d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png 1272w, https://substackcdn.com/image/fetch/$s_!T_3d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fef4521-c048-415c-b637-86c33a41eeef_1264x420.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Cette &#233;tape pr&#233;pare la compilation des assets front-end avec Webpacker ou Sprockets.</p><h3>3. Gems avec Bundler</h3><p>Pour profiter du cache Docker et &#233;viter de r&#233;installer les gems inutilement :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LpY9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LpY9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png 424w, https://substackcdn.com/image/fetch/$s_!LpY9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png 848w, https://substackcdn.com/image/fetch/$s_!LpY9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png 1272w, https://substackcdn.com/image/fetch/$s_!LpY9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LpY9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png" width="1262" height="274" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:274,&quot;width&quot;:1262,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:65651,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LpY9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png 424w, https://substackcdn.com/image/fetch/$s_!LpY9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png 848w, https://substackcdn.com/image/fetch/$s_!LpY9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png 1272w, https://substackcdn.com/image/fetch/$s_!LpY9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d2f74e4-c927-421e-9087-25f90cadb756_1262x274.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Docker invalide cette couche <strong>seulement</strong> si <code>Gemfile</code> ou <code>Gemfile.lock</code> changent, ce qui rend les rebuilds beaucoup plus rapides.</p><h3>4. Faux secrets et assets</h3><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!H0-C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!H0-C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png 424w, https://substackcdn.com/image/fetch/$s_!H0-C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png 848w, https://substackcdn.com/image/fetch/$s_!H0-C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png 1272w, https://substackcdn.com/image/fetch/$s_!H0-C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!H0-C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png" width="1258" height="272" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:272,&quot;width&quot;:1258,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:57207,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!H0-C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png 424w, https://substackcdn.com/image/fetch/$s_!H0-C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png 848w, https://substackcdn.com/image/fetch/$s_!H0-C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png 1272w, https://substackcdn.com/image/fetch/$s_!H0-C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0aad5d-4334-4804-99f8-8eab5a39a5d5_1258x272.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Le <code>.env</code> factice permet de satisfaire Rails sans exposer de vrais secrets ; qui sont inject&#233;s au runtime.</p><div><hr></div><h2>&#127919; &#201;tape 2 : production</h2><p>L&#8217;&#233;tape finale construit l&#8217;image de prod en mode minimal :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EjWB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EjWB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png 424w, https://substackcdn.com/image/fetch/$s_!EjWB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png 848w, https://substackcdn.com/image/fetch/$s_!EjWB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png 1272w, https://substackcdn.com/image/fetch/$s_!EjWB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EjWB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png" width="1268" height="142" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:142,&quot;width&quot;:1268,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:24686,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EjWB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png 424w, https://substackcdn.com/image/fetch/$s_!EjWB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png 848w, https://substackcdn.com/image/fetch/$s_!EjWB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png 1272w, https://substackcdn.com/image/fetch/$s_!EjWB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a26d12-5434-4958-8fe0-e918fac2013c_1268x142.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h3>Node.js facultatif</h3><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J22t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J22t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png 424w, https://substackcdn.com/image/fetch/$s_!J22t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png 848w, https://substackcdn.com/image/fetch/$s_!J22t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png 1272w, https://substackcdn.com/image/fetch/$s_!J22t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J22t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png" width="1258" height="162" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e441ebad-b271-4df0-8631-45768544ccf1_1258x162.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:162,&quot;width&quot;:1258,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:34173,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J22t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png 424w, https://substackcdn.com/image/fetch/$s_!J22t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png 848w, https://substackcdn.com/image/fetch/$s_!J22t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png 1272w, https://substackcdn.com/image/fetch/$s_!J22t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe441ebad-b271-4df0-8631-45768544ccf1_1258x162.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Je peux choisir de conserver Node.js pour debug ou m&#8217;en passer pour all&#233;ger l&#8217;image.</p><h3>Copie des artefacts</h3><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XaJZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XaJZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png 424w, https://substackcdn.com/image/fetch/$s_!XaJZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png 848w, https://substackcdn.com/image/fetch/$s_!XaJZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png 1272w, https://substackcdn.com/image/fetch/$s_!XaJZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XaJZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png" width="1256" height="204" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:204,&quot;width&quot;:1256,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55716,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XaJZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png 424w, https://substackcdn.com/image/fetch/$s_!XaJZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png 848w, https://substackcdn.com/image/fetch/$s_!XaJZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png 1272w, https://substackcdn.com/image/fetch/$s_!XaJZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9640f90-03ab-48e7-b89c-033304a7f2da_1256x204.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Seuls les gems et les assets pr&#233;compil&#233;s sont repris, tout le reste reste dans l&#8217;&#233;tape builder.</p><h3>S&#233;curit&#233; et droits</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UpnT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UpnT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png 424w, https://substackcdn.com/image/fetch/$s_!UpnT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png 848w, https://substackcdn.com/image/fetch/$s_!UpnT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png 1272w, https://substackcdn.com/image/fetch/$s_!UpnT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UpnT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png" width="1256" height="380" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:380,&quot;width&quot;:1256,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63173,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UpnT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png 424w, https://substackcdn.com/image/fetch/$s_!UpnT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png 848w, https://substackcdn.com/image/fetch/$s_!UpnT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png 1272w, https://substackcdn.com/image/fetch/$s_!UpnT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6da30560-c20e-4045-b85e-3b2a505a8dec_1256x380.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ex&#233;cuter l&#8217;app en non-root limite les risques en cas de faille.</p><h3>Exposition et lancement</h3><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hxLT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hxLT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png 424w, https://substackcdn.com/image/fetch/$s_!hxLT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png 848w, https://substackcdn.com/image/fetch/$s_!hxLT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png 1272w, https://substackcdn.com/image/fetch/$s_!hxLT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hxLT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png" width="1262" height="130" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:130,&quot;width&quot;:1262,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:24206,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/162967802?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hxLT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png 424w, https://substackcdn.com/image/fetch/$s_!hxLT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png 848w, https://substackcdn.com/image/fetch/$s_!hxLT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png 1272w, https://substackcdn.com/image/fetch/$s_!hxLT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d027da6-f61c-44c7-a114-b0bd707cc4c7_1262x130.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Cette derni&#232;re ligne reste simple et claire pour d&#233;marrer le serveur Rails.</p><div><hr></div><h2>&#9986;&#65039; Un <code>.dockerignore</code> chirurgical</h2><p>Chaque ligne compte pour all&#233;ger le contexte :</p><pre><code><code># dossiers syst&#232;mes et dev
.git/
.bundle/
node_modules/

# logs et tmp
log/
tmp/

# secrets et configs locales
.env*
config/master.key

# assets compil&#233;s
public/assets
public/packs
</code></code></pre><p>Ce <code>.dockerignore</code> emp&#234;che de copier tout ce qui n&#8217;est pas n&#233;cessaire au build.</p><div><hr></div><h2>&#128230; Un syst&#232;me de CI centralis&#233;</h2><p>J&#8217;ai centralis&#233; Dockerfile, <code>.dockerignore</code>, <code>database.yml.template</code> et <code>.env</code> factice dans un repo CI. Ce dossier est copi&#233; dans chaque projet pour garantir une coh&#233;rence et faciliter les mises &#224; jour.</p><div><hr></div><h2>Dockerfile complet</h2><p><a href="https://gist.github.com/Melrt/a3dc3d55db2c225b643032f1fefea031">Voir le Dockerfile complet</a></p><div><hr></div><h2>Pour aller plus loin</h2><h3>Docker</h3><ul><li><p>Documentation officielle : </p></li></ul><p>https://docs.docker.com/</p><ul><li><p>Guide multi-stage : <a href="https://docs.docker.com/build/building/multi-stage/">https://docs.docker.com/build/building/multi-stage/</a></p></li></ul><h3>S&#233;curit&#233;</h3><ul><li><p>Bonnes pratiques : <a href="https://docs.docker.com/develop/security-best-practices/">https://docs.docker.com/develop/security-best-practices/</a></p></li><li><p>Trivy : <a href="https://github.com/aquasecurity/trivy">https://github.com/aquasecurity/trivy</a></p></li></ul><h3>Performance</h3><ul><li><p>BuildKit : <a href="https://docs.docker.com/build/buildkit/">https://docs.docker.com/build/buildkit/</a></p></li></ul><div><hr></div><p>Ce Dockerfile m&#8217;a permis de gagner en rapidit&#233;, en l&#233;g&#232;ret&#233; et en s&#233;curit&#233;. J&#8217;esp&#232;re qu&#8217;il vous servira autant qu&#8217;il m&#8217;accompagne au quotidien !</p><p>&#8212; <em>Quentin</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🧑‍🔧 Nos partials devenaient ingérables. Les strict locals ont tout changé]]></title><description><![CDATA[Temps de lecture : 5 minutes]]></description><link>https://www.rubybiscuit.fr/p/cree-des-partials-robustes-et-maintenables</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/cree-des-partials-robustes-et-maintenables</guid><pubDate>Wed, 02 Apr 2025 14:02:27 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/467b89c9-0064-4596-b320-183283613dde_6463x8563.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la 32&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 581 abonn&#233;s &#129395;</p><div><hr></div><p><strong>Ruby Biscuit, c&#8217;est aussi votre meilleur alli&#233; pour trouver un job !</strong></p><p>&#127919; En ce moment, <strong>nous recherchons 5 profils Ruby confirm&#233;&#183;es ou interm&#233;diaires</strong> pour des postes au sein de notre r&#233;seau.<br>&#201;crivez-nous ou <a href="https://recrutement.rubybiscuit.fr/je-cherche-une-boite">postulez directement sur le site</a> !</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WzbZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WzbZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png 424w, https://substackcdn.com/image/fetch/$s_!WzbZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png 848w, https://substackcdn.com/image/fetch/$s_!WzbZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png 1272w, https://substackcdn.com/image/fetch/$s_!WzbZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WzbZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png" width="1456" height="840" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:840,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1044677,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/160412403?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WzbZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png 424w, https://substackcdn.com/image/fetch/$s_!WzbZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png 848w, https://substackcdn.com/image/fetch/$s_!WzbZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png 1272w, https://substackcdn.com/image/fetch/$s_!WzbZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4228675b-a946-407d-ae8a-89638fe87928_2798x1614.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture &#10084;&#65039;</p><div><hr></div><h2><strong>Cr&#233;e des partials robustes et maintenables avec les strict locals</strong></h2><p>Lorsque nos vues deviennent complexes ou grandes, ou que des parties doivent &#234;tre r&#233;utilis&#233;es ailleurs dans notre application, un d&#233;coupage en partial s'impose.</p><p>Nous avons souvent besoin d'acc&#233;der &#224; des variables dans ces partials et il n'est pas rare d'utiliser des variables d'instance qui viennent de notre action de controller car elles sont faciles d'acc&#232;s partout dans nos vues et dans nos partials, peu importe le niveau d'imbrication. Wait ... est ce que pratique rime avec maintenable ? &#129300;</p><p>L'aspect n&#233;gatif de cette m&#233;thode est qu'elle rend nos partials tr&#232;s d&#233;pendantes de nos actions de controller : si une autre vue souhaite utiliser notre partial, l'action de controller qui lui est associ&#233;e devra impl&#233;menter cette variable d'instance, sans que &#231;a ait vraiment forc&#233;ment du sens dans le contexte de cette action. <br>Pire, si ce controller impl&#233;mente d&#233;j&#224; une variable d'instance du m&#234;me nom, on pourrait se retrouver &#224; utiliser dans notre partial une variable erron&#233;e dans ce contexte sans le savoir.</p><p><strong>Chez Capsens nous avons pris la d&#233;cision d'interdire l'utilisation des variables d'instance dans nos partials pour privil&#233;gier l'utilisation syst&#233;matique des </strong><code>locals</code><strong>.</strong></p><p>Cela a l'avantage de faciliter la lecture des partials, on sait exactement d'o&#249; viennent nos variables et nos partials deviennent auto-suffisantes : il suffit de passer les bonnes variables en param&#232;tres pour l'utiliser : moins de magie, moins d'indirection et des tests plus faciles &#224; &#233;crire.</p><p>Afin de rendre nos partials robustes et de s'assurer qu'on n'oublie pas de leur passer telle ou telle variable, Rails nous met &#224; disposition un outil tr&#232;s pratique : <strong>les strict locals</strong>.</p><p>Cette notation permet de d&#233;clarer quelles sont les variables que notre partial accepte et lesquelles sont obligatoires ou optionnelles. En cas de variable manquante, ou en trop, notre partial levera une erreur automatiquement. Elle prend la forme d'un "commentaire magique", avec une syntaxe sp&#233;cifique dans lequel nous allons lister les param&#232;tres de notre partial.</p><p>Exemple :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xAu4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xAu4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png 424w, https://substackcdn.com/image/fetch/$s_!xAu4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png 848w, https://substackcdn.com/image/fetch/$s_!xAu4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png 1272w, https://substackcdn.com/image/fetch/$s_!xAu4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xAu4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png" width="1166" height="760" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:760,&quot;width&quot;:1166,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:111011,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/160412403?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xAu4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png 424w, https://substackcdn.com/image/fetch/$s_!xAu4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png 848w, https://substackcdn.com/image/fetch/$s_!xAu4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png 1272w, https://substackcdn.com/image/fetch/$s_!xAu4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58ea61cc-f0f6-46f1-a102-6efbb70689e9_1166x760.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">app/views/projetcs/_subscription.html.erb</figcaption></figure></div><p></p><p></p><p>Dans cet exemple notre partial prend deux valeurs : une <code>subscription</code>, obligatoire (la partial va donc lever une exception si jamais on ne lui passe pas cette valeur) et une autre <code>display_rate</code> qui est pass&#233;e optionnellement et sera par default &#224; <code>true</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ny7L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ny7L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png 424w, https://substackcdn.com/image/fetch/$s_!ny7L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png 848w, https://substackcdn.com/image/fetch/$s_!ny7L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!ny7L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ny7L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png" width="1166" height="1264" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1264,&quot;width&quot;:1166,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:205416,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/160412403?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ny7L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png 424w, https://substackcdn.com/image/fetch/$s_!ny7L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png 848w, https://substackcdn.com/image/fetch/$s_!ny7L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!ny7L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc5b2fea9-28d0-4907-9c9f-5026791d4829_1166x1264.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://guides.rubyonrails.org/action_view_overview.html#strict-locals">N'oubliez pas d'aller jeter un coup d'oeil &#224; la doc </a>!</p><h2>BONUS - Tests des partials</h2><p>Nous avons pris l'habitude de tester unitairement nos partials afin de s'assurer que nos vues ne g&#233;n&#232;rent pas d'erreurs, ce qui &#233;vite des tests de requ&#234;tes lourds, compliqu&#233;s, avec des tonnes de contexte selon les conditions d'affichage des &#233;l&#233;ments dans la vue associ&#233;e. L'utilisation des locals et des strict locals par rapport aux variables d'instances nous permet de simplifier le setup de nos tests : on peut passer simplement nos variables en param&#232;tre de notre partial plut&#244;t que de devoir tricher pour cr&#233;er des variables d'instances et un faux controller.</p><p>Avec RSpec, ajouter ces lignes dans le fichier <code>spec/rails_helper.rb</code> permettra d'acc&#233;der aux m&#233;thodes de helper de Devise pour, par exemple, avoir un utilisateur connect&#233; dans notre test :</p><pre><code><code>config.include Devise::Test::ControllerHelpers, type: :view</code>
<code>config.include Devise::Test::IntegrationHelpers, type: :view</code></code></pre><p>Exemple d'un test unitaire de partial :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qFcw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qFcw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png 424w, https://substackcdn.com/image/fetch/$s_!qFcw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png 848w, https://substackcdn.com/image/fetch/$s_!qFcw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!qFcw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qFcw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png" width="1456" height="1030" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1030,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:194277,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/160412403?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qFcw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png 424w, https://substackcdn.com/image/fetch/$s_!qFcw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png 848w, https://substackcdn.com/image/fetch/$s_!qFcw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png 1272w, https://substackcdn.com/image/fetch/$s_!qFcw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f400eab-dc57-4e68-8206-f69959072ad3_1482x1048.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><br>&#8212; <em>Eliot</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🏭 ActiveRecord : Comment on éviter les pièges de performance en production]]></title><description><![CDATA[Temps de lecture : 5 minutes]]></description><link>https://www.rubybiscuit.fr/p/activerecord-eviter-les-pieges-de</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/activerecord-eviter-les-pieges-de</guid><pubDate>Wed, 05 Mar 2025 15:02:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4b1bf38d-e34f-47dc-9497-9f3ec0bf9175_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la 31&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 575 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w5Nb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" width="1456" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><h2><strong>ActiveRecord  : &#201;viter les pi&#232;ges de performance en production</strong></h2><p>R&#233;cemment, en ajoutant une fonctionnalit&#233; de g&#233;n&#233;ration d&#8217;&#233;ch&#233;ancier &#224; l&#8217;une de nos applications Rails, je me suis retrouv&#233;e face &#224; un probl&#232;me de performance.</p><p>Notre syst&#232;me fonctionne ainsi : un projet est publi&#233;, des investisseurs y investissent et en retour, ils per&#231;oivent un remboursement avec un taux d&#8217;int&#233;r&#234;t. Du crowdfunding globalement. Pour aider les administrateurs &#224; organiser ces remboursements, nous devons g&#233;n&#233;rer un &#233;ch&#233;ancier pour chaque investisseur, souvent &#233;tal&#233; sur plusieurs mois, voire plusieurs ann&#233;es.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!faJj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!faJj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png 424w, https://substackcdn.com/image/fetch/$s_!faJj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png 848w, https://substackcdn.com/image/fetch/$s_!faJj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png 1272w, https://substackcdn.com/image/fetch/$s_!faJj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!faJj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png" width="1348" height="662" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/abb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:662,&quot;width&quot;:1348,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:249291,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/158438615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!faJj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png 424w, https://substackcdn.com/image/fetch/$s_!faJj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png 848w, https://substackcdn.com/image/fetch/$s_!faJj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png 1272w, https://substackcdn.com/image/fetch/$s_!faJj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabb3ef25-6908-4405-9b2c-d8200749df06_1348x662.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Pour vous donner une id&#233;e, dans mon cas, il s'agissait d&#8217;environ <strong>5 000 investisseurs</strong>, chacun ayant un &#233;ch&#233;ancier sur <strong>30 mois</strong>, soit un total de <strong>150 000 &#233;ch&#233;ances</strong> &#224; calculer et g&#233;n&#233;rer.</p><p>J&#8217;ai donc cod&#233; ma fonctionnalit&#233; tranquillement, test&#233; son bon fonctionnement, puis ouvert ma pull request (merge request sur GitLab) en attendant sa relecture.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ta-a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ta-a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif 424w, https://substackcdn.com/image/fetch/$s_!Ta-a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif 848w, https://substackcdn.com/image/fetch/$s_!Ta-a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif 1272w, https://substackcdn.com/image/fetch/$s_!Ta-a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ta-a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif" width="500" height="375" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:375,&quot;width&quot;:500,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:929009,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/158438615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ta-a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif 424w, https://substackcdn.com/image/fetch/$s_!Ta-a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif 848w, https://substackcdn.com/image/fetch/$s_!Ta-a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif 1272w, https://substackcdn.com/image/fetch/$s_!Ta-a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82e5e2e1-addf-4a4a-a8eb-26707a0f026f_500x375.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Tout allait bien jusqu'&#224; ce que mon relecteur pose <em>la</em> question qui f&#226;che : <strong>"Tu as test&#233; ton script avec des volumes similaires &#224; ceux de la production ?"</strong></p><p>La r&#233;ponse &#233;tait &#233;vidente : <strong>"Non."</strong></p><p>Je m&#8217;y mets donc&#8230; et l&#224;, catastrophe. Mon service met plusieurs minutes &#224; s&#8217;ex&#233;cuter. C&#8217;est interminable. Pourtant, j&#8217;avais fait attention &#224; la performance&#8230; du moins c&#8217;est ce que je croyais.</p><p>Il faut donc <strong>remettre les mains dans le cambouis</strong>. C&#8217;est parti !</p><p>Si vous voulez suivre en d&#233;tail la suite de l&#8217;article, vous pouvez cloner ce repo :<br>&#128073; <a href="https://github.com/CapSens/ruby-biscuit-active-record-performance">https://github.com/CapSens/ruby-biscuit-active-record-performance.git</a><br>Il contient une version ultra simplifi&#233;e de notre probl&#233;matique du jour.</p><p>Notre domaine &#233;tant le <strong>crowdfunding</strong>, certaines logiques m&#233;tier peuvent vous &#234;tre inconnues. Voici quelques &#233;l&#233;ments essentiels pour ne pas vous perdre :</p><ul><li><p>Un &#233;ch&#233;ancier doit pouvoir &#234;tre g&#233;n&#233;r&#233; ou reg&#233;n&#233;r&#233; &#224; tout moment. Avant d&#8217;en g&#233;n&#233;rer un nouveau, il est donc essentiel de supprimer l&#8217;ancien.</p></li><li><p>Un projet est g&#233;r&#233; par un porteur de projet, qui doit rembourser ses investisseurs en r&#233;partissant une somme pr&#233;cise incluant des int&#233;r&#234;ts, selon un nombre d&#8217;&#233;ch&#233;ances d&#233;fini contractuellement. Ces &#233;ch&#233;ances sont appel&#233;es <code>borrower_terms</code>.</p></li><li><p>Les &#233;ch&#233;ances per&#231;ues par les investisseurs (les remboursements qu&#8217;ils re&#231;oivent) sont quant &#224; elles appel&#233;es <code>lender_terms</code>.</p></li></ul><h2>Mise en place d'un script de benchmark</h2><p>Un <strong>benchmark</strong> est un test qui permet de mesurer la rapidit&#233; et l'efficacit&#233; de notre code.<br>Il existe diff&#233;rents outils pour cela, plus ou moins puissants selon les besoins. L&#8217;important est de choisir celui qui s&#8217;adapte le mieux &#224; votre cas d&#8217;usage.</p><p>Ma d&#233;marche &#224; &#233;t&#233; la suivante :</p><ol><li><p>Mettre en place un <strong>script de benchmark simple</strong>, que je pourrais relancer apr&#232;s chaque modification du code.</p></li><li><p>Tester diff&#233;rentes optimisations pour <strong>gagner en performance</strong> et identifier des <em>quick wins</em>.</p></li></ol><p>Pour notre script de benchmark, nous avons d&#233;cid&#233; d&#8217;impl&#233;menter deux m&#233;thodes :</p><ul><li><p><code>benchmark_memory</code> : utilise la gem <code>benchmark_memory</code> pour &#233;valuer la quantit&#233; de m&#233;moire utilis&#233;e par notre code et d&#233;tecter les fuites m&#233;moire (ce qui n&#8217;est pas lib&#233;r&#233;).</p></li><li><p><code>benchmark_time</code> : une m&#233;thode <em>custom</em> qui mesure le <strong>temps d&#8217;ex&#233;cution</strong> du code.</p></li></ul><p>Ces deux m&#233;thodes prennent en param&#232;tre : le nombre de souscriptions et le nombre d&#8217;&#233;ch&#233;ances souhait&#233;es</p><p>&#192; chaque appel de ces m&#233;thodes :</p><ol><li><p>Un <strong>jeu de donn&#233;es</strong> est g&#233;n&#233;r&#233;.</p></li><li><p>Deux g&#233;n&#233;rateurs d&#8217;&#233;ch&#233;anciers sont lanc&#233;s :</p></li></ol><ul><li><p><strong>L&#8217;un avec la version initiale du code</strong></p></li><li><p><strong>L&#8217;autre avec la version am&#233;lior&#233;e</strong><br>Cela permet d&#8217;observer les gains de performance <strong>sans modifier le code originel</strong>.</p></li></ul><ol><li><p>Tout cela s&#8217;ex&#233;cute dans une <strong>transaction Active Record</strong>, ce qui permet de revenir facilement &#224; l&#8217;&#233;tat initial apr&#232;s chaque test.</p></li></ol><p>Dans le repo, vous trouverez le script de benchmark ici : <a href="https://github.com/CapSens/ruby-biscuit-active-record-performance/blob/main/app/scripts/test_script.rb">app/scripts/test_script.rb</a>.</p><p>Et voici le code de g&#233;n&#233;ration de l&#8217;&#233;ch&#233;ancier :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_yj4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_yj4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png 424w, https://substackcdn.com/image/fetch/$s_!_yj4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png 848w, https://substackcdn.com/image/fetch/$s_!_yj4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png 1272w, https://substackcdn.com/image/fetch/$s_!_yj4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_yj4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png" width="1456" height="1841" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1841,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:377328,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/158438615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_yj4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png 424w, https://substackcdn.com/image/fetch/$s_!_yj4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png 848w, https://substackcdn.com/image/fetch/$s_!_yj4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png 1272w, https://substackcdn.com/image/fetch/$s_!_yj4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac5239ad-a52f-405f-bedb-ab6b8bb70c1a_1598x2020.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Spoiler alert : J'ai r&#233;ussi &#224; diviser le temps d'ex&#233;cution par ~ 6 et la m&#233;moire utilis&#233;e par ~ 3.</p><p>Ci dessous quelques exemples de benchmark fait durant l'optimisation :</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!n-UH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!n-UH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png 424w, https://substackcdn.com/image/fetch/$s_!n-UH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png 848w, https://substackcdn.com/image/fetch/$s_!n-UH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png 1272w, https://substackcdn.com/image/fetch/$s_!n-UH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!n-UH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png" width="1340" height="170" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:170,&quot;width&quot;:1340,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:117585,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/158438615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!n-UH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png 424w, https://substackcdn.com/image/fetch/$s_!n-UH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png 848w, https://substackcdn.com/image/fetch/$s_!n-UH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png 1272w, https://substackcdn.com/image/fetch/$s_!n-UH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d7508a5-9c46-49cd-90f1-baab646994d3_1340x170.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><em>Ici on peut voir qu'entre le code originel et la version am&#233;lior&#233;e on a diminu&#233; presque par 6 le temps en millisecondes</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xc93!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xc93!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png 424w, https://substackcdn.com/image/fetch/$s_!xc93!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png 848w, https://substackcdn.com/image/fetch/$s_!xc93!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png 1272w, https://substackcdn.com/image/fetch/$s_!xc93!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xc93!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png" width="1342" height="836" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:836,&quot;width&quot;:1342,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:470461,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/158438615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xc93!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png 424w, https://substackcdn.com/image/fetch/$s_!xc93!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png 848w, https://substackcdn.com/image/fetch/$s_!xc93!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png 1272w, https://substackcdn.com/image/fetch/$s_!xc93!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba6e9cc-be30-48bc-b008-9d47067136b1_1342x836.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Ici dans le premier exemple on divise par 3 la taille de la m&#233;moire et le nombre d'objets en m&#233;moire</em></p><p></p><p>Pas mal non ? Allons voir ce que j'ai modifi&#233; !</p><h3>Les am&#233;liorations</h3><ol><li><p><code>destroy_all</code> vs <code>delete_all</code></p></li><li><p><code>includes</code> or not <code>includes</code></p></li><li><p><code>find_each</code> vs <code>each</code></p></li><li><p><code>activerecord-import</code></p></li></ol><p></p><p>Mesurer le temps d&#8217;ex&#233;cution global, c&#8217;est bien, mais comprendre quels morceaux du code sont les plus lents, c&#8217;est mieux. C&#8217;est en analysant l&#8217;ex&#233;cution &#233;tape par &#233;tape que l&#8217;on identifie o&#249; les am&#233;liorations sont n&#233;cessaires.</p><p>Une bonne pratique &#224; adopter est d'ajouter des messages de debug aux diff&#233;rentes &#233;tapes de votre script. Cela permet d&#8217;observer quelles parties prennent le plus de temps et ou des optimisations sont possibles.</p><p></p><h2>1&#65039;&#8419; <code>destroy_all</code> vs <code>delete_all</code></h2><p>J&#8217;ai commenc&#233; par ajouter un message de debug qui m&#8217;indiquait &#224; chaque fois qu&#8217;une &#233;ch&#233;ance &#233;tait g&#233;n&#233;r&#233;e. &#192; ma grande surprise, j&#8217;ai attendu plusieurs minutes avant que la toute premi&#232;re &#233;ch&#233;ance apparaisse.</p><p><strong>Le probl&#232;me : un </strong><code>destroy_all</code><strong> trop gourmand</strong></p><p>En y regardant de plus pr&#232;s, j&#8217;ai compris pourquoi. Avant de g&#233;n&#233;rer les nouvelles &#233;ch&#233;ances, mon script supprimait les anciennes avec <code>destroy_all</code>.</p><p>Supprimer 150 000 &#233;ch&#233;ances avec <code>destroy_all</code> d&#233;clenchait les callbacks Rails sur chaque suppression. M&#234;me si chaque suppression ne prenait que 0.001 seconde, cela repr&#233;sentait d&#233;j&#224; 150 secondes d&#8217;attente avant m&#234;me de commencer &#224; g&#233;n&#233;rer de nouvelles &#233;ch&#233;ances &#128558;&#8205;&#128168;.</p><p>En rempla&#231;ant <code>destroy_all</code> par <code>delete_all</code>, j&#8217;ai drastiquement r&#233;duit le nombre de requ&#234;tes SQL. On est pass&#233; de 150 000 requ&#234;tes &#224; une seule.</p><p><strong>Attention</strong> : <code>destroy_all</code> reste utile lorsque vous avez besoin de d&#233;clencher des callbacks de suppression, notamment pour g&#233;rer les objets associ&#233;s. Ce n&#8217;est donc pas une m&#233;thode &#224; syst&#233;matiquement remplacer par <code>delete_all</code>, mais dans mon cas, l&#8217;optimisation &#233;tait pertinente.</p><p></p><h2>2&#65039;&#8419; <code>includes</code> or not <code>includes</code></h2><p>Lorsque l'on manipule un grand nombre de donn&#233;es, il est essentiel de bien g&#233;rer le chargement des ressources. Sinon, on risque de multiplier inutilement les requ&#234;tes SQL, ce qui peut rapidement d&#233;grader les performances.</p><p><strong>Le probl&#232;me :  </strong><code>N+1 </code></p><p>Prenons un extrait de la version non optimis&#233;e du code :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BQL6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BQL6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png 424w, https://substackcdn.com/image/fetch/$s_!BQL6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png 848w, https://substackcdn.com/image/fetch/$s_!BQL6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png 1272w, https://substackcdn.com/image/fetch/$s_!BQL6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BQL6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png" width="1120" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:1120,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66531,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/158438615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BQL6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png 424w, https://substackcdn.com/image/fetch/$s_!BQL6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png 848w, https://substackcdn.com/image/fetch/$s_!BQL6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png 1272w, https://substackcdn.com/image/fetch/$s_!BQL6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e842533-bf8d-4770-80d5-d6e4a7958b88_1120x400.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Dans un premier temps, on r&#233;cup&#232;re les souscriptions avec <code>find_each</code>, qui charge les donn&#233;es par batchs de 1 000, ce qui est d&#233;j&#224; une bonne pratique.</p><p>Mais ensuite, pour chaque souscription, on r&#233;cup&#232;re la derni&#232;re &#233;ch&#233;ance via <code>subscription.lender_terms.last</code>, ce qui g&#233;n&#232;re une requ&#234;te suppl&#233;mentaire par souscription.</p><p>Si on a 5 000 souscriptions, on se retrouve donc avec :</p><ul><li><p><strong>5 requ&#234;tes</strong> pour charger les souscriptions (<code>find_each</code> traite 1 000 &#233;l&#233;ments &#224; la fois)</p></li><li><p><strong>5 000 requ&#234;tes</strong> pour r&#233;cup&#233;rer les &#233;ch&#233;ances</p></li></ul><p>Soit un total de <strong>5005 requ&#234;tes</strong> SQL...</p><p>Comment arranger &#231;a ? En utilisant <code>includes(:lender_terms)</code>, on demande &#224; Active Record de r&#233;cup&#233;rer les &#233;ch&#233;ances en <strong>une seule requ&#234;te</strong> par batch :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!D6Ec!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D6Ec!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png 424w, https://substackcdn.com/image/fetch/$s_!D6Ec!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png 848w, https://substackcdn.com/image/fetch/$s_!D6Ec!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png 1272w, https://substackcdn.com/image/fetch/$s_!D6Ec!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D6Ec!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png" width="1352" height="436" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:436,&quot;width&quot;:1352,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:77296,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.rubybiscuit.fr/i/158438615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!D6Ec!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png 424w, https://substackcdn.com/image/fetch/$s_!D6Ec!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png 848w, https://substackcdn.com/image/fetch/$s_!D6Ec!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png 1272w, https://substackcdn.com/image/fetch/$s_!D6Ec!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0a4a092-e542-4c95-a8fb-68b2b92e2f7e_1352x436.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Nous avons maintenant : 5 requ&#234;tes avec le find_each (5*1000) + 1 requ&#234;tes par batch pour r&#233;cup&#233;rer les &#233;ch&#233;ances, soit <strong>10 requ&#234;tes</strong> SQL contre 10 000 dans la version initiale &#128565;.</p><p></p><h2>3&#65039;&#8419; <code>find_each</code> vs <code>each</code></h2><p>Reprenons notre exemple <code>project.subscriptions.find_each</code>, si on remplace le <code>find_each</code> par un <code>each</code> on se retrouve &#224; charger en m&#233;moire un array avec 5 000 objets ruby, &#231;a fait beaucoup pas vrai ?</p><p>En utilisant <code>find_each</code>, par d&#233;faut on aura des batchs de 1 000 donc on chargera en m&#233;moire que 1 000 objets ruby. Ce qui est d&#233;j&#224; correcte, surtout dans notre cas ou ca ne n&#233;cessitera que 5 requ&#234;tes au total.</p><p>C'est g&#233;nial, on l'utilise partout alors ! ...Pas vraiment si on regarde le bout de code suivant</p><pre><code><code>borrower_terms = project.borrower_terms</code>

<code>project.subscriptions.find_each do |subscription|</code>
<code>  previous_investor_term = subscription.lender_terms.last</code>
<code>  current_investor_term = nil</code>
<code>  </code>
<code>  borrower_terms.find_each do |borrower_term| # &lt;--</code>
<code>  end</code>
<code>end</code>
</code></pre><p>Vous ne le voyez peut-&#234;tre pas mais c'est contre productif &#224; la ligne <code>borrower_terms.find_each</code>. Pourquoi ?<br><br>La variable <code>borrower_terms</code> est d&#233;j&#224; r&#233;cup&#233;r&#233; plus haut et elle sera la m&#234;me pour l'it&#233;ration de chaque souscription, le probl&#232;me c'est que le <code>find_each</code> d&#233;clenche une nouvelle requ&#234;te SQL &#224; chaque fois alors que la donn&#233;e n'a pas chang&#233;. On fait donc plusieurs fois 5*1000 requ&#234;tes, au lieu de le faire 1 seul fois.<br>La solution ici est simple, utilis&#233; <code>.each</code> qui nous permet d'it&#233;rer sans faire de requ&#234;te suppl&#233;mentaire car les ressources sont d&#233;j&#224; charg&#233;es.</p><p>A savoir : <code>find_each</code> permet de faire des batch mais supprime &#233;galement toute notion d'ordre dans la requ&#234;te initiale. Il est important de l'avoir en t&#234;te et de ne pas l'utiliser si l'ordre de vos resources est important.</p><p></p><h2>4&#65039;&#8419; activerecord-import</h2><p><a href="https://github.com/zdennis/activerecord-import">activerecord-import</a> est une superbe gem qui nous permet de cr&#233;er plusieurs ressources en une seule requ&#234;te. Je vous invite &#224; y jeter un oeil si vous manipulez beaucoup de donn&#233;es ou que vous fa&#238;tes beaucoup de migrations.<br><br>Pas besoin d'explication tr&#232;s d&#233;taill&#233;e, avec plus de 150 000 &#233;ch&#233;ances &#224; cr&#233;er, &#231;a ne peut qu'am&#233;liorer la performance de notre code. Si on revient en arri&#232;re sur le premier point concernant la suppression des &#233;ch&#233;ances c'est un peu le m&#234;me principe, pourquoi s'emb&#234;ter &#224; faire autant de requ&#234;tes qu'il y a de ressources alors qu'on pourrait le faire en faire une seule.</p><p>Pour conclure j'ajouterai &#224; tout cela, qu'il est important, dans cette m&#233;thode de test avec un benchmark, de ne pas sous estimer le temps d'affichage des logs. Pour se rapprocher du temps r&#233;el d'ex&#233;cution il faudra penser &#224; retirer les logs, vous pouvez le faire facilement avec la commande <code>ActiveRecord::Base.logger = nil</code><br><br>La performance dans une application est un sujet passionnant et tr&#232;s tr&#232;s vaste. Bien qu'il soit toujours important de bien connaitre son outil de travail pour &#233;viter certains &#233;cueils, il faut aussi garder en t&#234;te que plus on avance dans l'optimisation plus le rapport temps pass&#233; / gain est faible. Heureusement pour nous, la communaut&#233; Ruby est tr&#232;s active sur le sujet et beaucoup de recherches sont men&#233;es, notamment chez Shopify pour am&#233;liorer les performances de notre langage pr&#233;f&#233;r&#233;.</p><p>Internet regorge d'articles sur ces travaux alors si vous avez les reins solides n'h&#233;sitez pas &#224; vous plonger dans le sujet.</p><p></p><p><strong>Une liste d'outils inter&#233;ssants pour optimiser vos applications</strong></p><p><strong>ActiveRecord :</strong></p><ul><li><p>Importer des records en masse : https://github.com/zdennis/activerecord-import</p></li><li><p>Traquer les N+1 : https://github.com/flyerhzm/bullet</p></li></ul><p><strong>M&#233;moire / CPU :</strong></p><ul><li><p>Profiler ruby : https://github.com/tmm1/stackprof</p></li><li><p>Similaire au module Benchmark mais pour la m&#233;moire : https://github.com/michaelherold/benchmark-memory</p></li><li><p>Un benchmark m&#233;moire un peu plus complet fait par thoughtbot : https://github.com/SamSaffron/memory_profiler</p></li></ul><p><strong>RSpec :</strong></p><ul><li><p>Une boite &#224; outil pour am&#233;liorer la performance de votre suite de test : https://github.com/test-prof/test-prof</p></li></ul><p></p><p>&#8212; <em>David &amp; Quentin</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🧾 Générer des PDF dynamiques en Rails : notre approche avec Pdftk]]></title><description><![CDATA[Au programme aujourd&#8217;hui :]]></description><link>https://www.rubybiscuit.fr/p/remplir-un-template-pdf-avec-pdftk</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/remplir-un-template-pdf-avec-pdftk</guid><pubDate>Wed, 05 Feb 2025 15:00:45 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e990aecf-5968-4f95-b7d1-c77c698260e1_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Au programme aujourd&#8217;hui :</strong></p><ul><li><p><strong>Remplir un template PDF avec Pdftk. </strong><em>par Stanislas</em></p></li></ul><p><em>Temps de lecture :<strong> 5 minutes</strong></em></p><div><hr></div><p>Hello les petits Biscuits !</p><p>Bienvenue sur la 30&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 555 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w5Nb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" width="1456" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><h2><strong>Remplir un template PDF avec Pdftk</strong></h2><p>Bonjour &#224; tous ! Ce mois-ci nous allons voir comment remplir dynamiquement un template PDF, c'est parti &#128071;&#127995;</p><p><strong>Mise en place</strong></p><p>Nous allons avoir besoin d'installer <code>pdftk</code> sur notre machine, sur lequel se base la gem <a href="https://github.com/jkraemer/pdf-forms#fdfxfdf-creation">pdf_forms</a></p><p>&#201;tapes :<br>1. Installation de <code>pdftk</code> et <code>pdf_forms</code> <br>2. Configuration &#224; mettre en place<br>3. Comprendre comment assigner les variables sur le PDF<br>4. Cr&#233;ation d'un <code>dictionary</code> pour assigner les variables<br>5. G&#233;n&#233;ration du PDF</p><p>Pr&#233;-requis : Doc PDF avec variables assign&#233;es.</p><h4>1) Installation de <code>pdftk</code> et <code>pdf_forms</code></h4><p>&#8594; Installer <code>pdftk</code> sur sa machine avec</p><pre><code><code>brew install pdftk-java </code></code></pre><p>Pourquoi suffixer avec <code>java</code> ? Parce homebrew nous le conseille sur Mac &#224; partir du M1 .<br>&#9888;&#65039; Par d&#233;faut homebrew va vouloir faire un update g&#233;n&#233;ral, pour &#233;viter cela et se pr&#233;munir de conflits potentiels on va pouvoir pr&#233;fixer avec <code>HOMEBREW_NO_AUTO_UPDATE=1</code>.</p><pre><code><code>HOMEBREW_NO_AUTO_UPDATE=1 brew install pdftk-java</code></code></pre><p>&#8594; Mettre <code>pdf-forms</code> dans le Gemfile et <code>bundle install</code></p><h4>2) Configuration &#224; mettre en place</h4><p>&#192; mettre dans <code>application.rb</code> &#128071; </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!94BC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!94BC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png 424w, https://substackcdn.com/image/fetch/$s_!94BC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png 848w, https://substackcdn.com/image/fetch/$s_!94BC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png 1272w, https://substackcdn.com/image/fetch/$s_!94BC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!94BC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png" width="1122" height="688" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:688,&quot;width&quot;:1122,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:131359,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!94BC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png 424w, https://substackcdn.com/image/fetch/$s_!94BC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png 848w, https://substackcdn.com/image/fetch/$s_!94BC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png 1272w, https://substackcdn.com/image/fetch/$s_!94BC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e951c23-b8a3-463e-88f7-06edce44fa16_1122x688.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Se cr&#233;er un petit poro dans le dossier qui vous arrange &#224; l'interieur de <code>app/</code> pour qu'il soit autoload&#233;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hjlH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hjlH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png 424w, https://substackcdn.com/image/fetch/$s_!hjlH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png 848w, https://substackcdn.com/image/fetch/$s_!hjlH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png 1272w, https://substackcdn.com/image/fetch/$s_!hjlH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hjlH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png" width="1454" height="2416" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2416,&quot;width&quot;:1454,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:409583,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hjlH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png 424w, https://substackcdn.com/image/fetch/$s_!hjlH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png 848w, https://substackcdn.com/image/fetch/$s_!hjlH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png 1272w, https://substackcdn.com/image/fetch/$s_!hjlH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf6a9c90-eaa6-492a-93b4-6ac45c9b1f14_1454x2416.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h4>3) Comprendre comment assigner les variables sur le PDF</h4><p>Maintenant qu'on a fait la mise en place et qu'on a un PDF vide, comment va-t-on pouvoir dire qu'on veut telle donn&#233;e &#224; tel endroit ?<br>Tout se passe dans la configuration du PDF, et plus pr&#233;cis&#233;ment dans la partie "Pr&#233;parer un formulaire" sur Adobe Acrobat (ou bien <a href="https://www.sejda.com">sejda</a>, voir fin de l'article).</p><p>Une fois que vous avez cr&#233;&#233; votre champ, acc&#233;dez aux "propri&#233;t&#233;s" de ce dernier :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YbEC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YbEC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png 424w, https://substackcdn.com/image/fetch/$s_!YbEC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png 848w, https://substackcdn.com/image/fetch/$s_!YbEC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png 1272w, https://substackcdn.com/image/fetch/$s_!YbEC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YbEC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png" width="908" height="428" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/caed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:428,&quot;width&quot;:908,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:107863,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YbEC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png 424w, https://substackcdn.com/image/fetch/$s_!YbEC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png 848w, https://substackcdn.com/image/fetch/$s_!YbEC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png 1272w, https://substackcdn.com/image/fetch/$s_!YbEC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaed04b1-aeb8-402d-b9a1-5ad491958e19_908x428.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>&#8594; Pour les champs string pas trop de soucis de ce c&#244;t&#233; l&#224;, vous pouvez les appeler comme vous voulez. Par exemple <code>profile_type</code>, <code>civility</code>, <code>toto</code>, etc. Attention tout de m&#234;me &#224; ne pas avoir de doublon.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m1Sa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m1Sa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png 424w, https://substackcdn.com/image/fetch/$s_!m1Sa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png 848w, https://substackcdn.com/image/fetch/$s_!m1Sa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png 1272w, https://substackcdn.com/image/fetch/$s_!m1Sa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m1Sa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png" width="909" height="851" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bea87353-0550-4570-a981-d65963bc9f08_909x851.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:851,&quot;width&quot;:909,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:155368,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!m1Sa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png 424w, https://substackcdn.com/image/fetch/$s_!m1Sa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png 848w, https://substackcdn.com/image/fetch/$s_!m1Sa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png 1272w, https://substackcdn.com/image/fetch/$s_!m1Sa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbea87353-0550-4570-a981-d65963bc9f08_909x851.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>&#8594; En revanche pour les checkboxes soyez attentifs. M&#234;me si l'on peut &#233;galement les appeler comme on le souhaite, <code>first_checkbox</code> par exemple, vous allez devoir renseigner une "valeur d'exportation". Cette derni&#232;re d&#233;terminera si la checkbox est coch&#233;e ou non. Dans l'exemple suivant, j'ai choisi <code>1</code>. En d'autres termes, une autre valeur que <code>1</code> n'aura aucun effet.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c1oc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c1oc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png 424w, https://substackcdn.com/image/fetch/$s_!c1oc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png 848w, https://substackcdn.com/image/fetch/$s_!c1oc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png 1272w, https://substackcdn.com/image/fetch/$s_!c1oc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c1oc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png" width="905" height="723" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:723,&quot;width&quot;:905,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:160807,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!c1oc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png 424w, https://substackcdn.com/image/fetch/$s_!c1oc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png 848w, https://substackcdn.com/image/fetch/$s_!c1oc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png 1272w, https://substackcdn.com/image/fetch/$s_!c1oc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd916d9d6-4f7b-41c3-b86e-fe9c2a8340f8_905x723.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>4) Cr&#233;ation d'un <code>dictionary</code> pour assigner les variables</h4><p>Maintenant qu'on a configur&#233; les champs dans le PDF il va falloir les remplir, et pour cela on va se cr&#233;er un dictionary nous permettre d'extraire la logique de mise en forme des donn&#233;es dans un objet dont c'est la seule responsabilit&#233; et qui sera tr&#232;s facilement testable. Celui-ci va nous renvoyer un hash que l'on pourra utiliser pour la g&#233;n&#233;ration du document.</p><p>Si l'on reprend les champs pr&#233;c&#233;demment cit&#233;s cela donnera :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1SvS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1SvS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png 424w, https://substackcdn.com/image/fetch/$s_!1SvS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png 848w, https://substackcdn.com/image/fetch/$s_!1SvS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png 1272w, https://substackcdn.com/image/fetch/$s_!1SvS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1SvS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png" width="1020" height="976" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0b26503-5862-4660-adf2-171344c0aa43_1020x976.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:976,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:119740,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1SvS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png 424w, https://substackcdn.com/image/fetch/$s_!1SvS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png 848w, https://substackcdn.com/image/fetch/$s_!1SvS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png 1272w, https://substackcdn.com/image/fetch/$s_!1SvS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b26503-5862-4660-adf2-171344c0aa43_1020x976.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Maintenant si l'on est pas certain du nom des champs ou bien que l'on souhaite v&#233;rifier leurs noms dans notre document, nous pouvons ex&#233;cuter les commandes suivantes :</p><pre><code><code>pdftk = PdfForms.new</code>
<code>pdftk.get_field_names(mon_document.download)</code></code></pre><p>Ce qui va nous retourner un array avec le nom des champs pr&#233;sents dans le PDF :</p><pre><code><code>["profile_type",</code>
<code>"civility",</code>
<code>"toto",</code>
<code>"first_checkbox"]</code></code></pre><p>Pratique pour &#233;viter les confusions !</p><h4>5) G&#233;n&#233;ration du PDF</h4><p>Tout est en place, il ne reste plus qu'&#224; g&#233;n&#233;rer notre document et l'enregistrer en db. Vous pouvez vous cr&#233;er un service comme suit :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eCFS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eCFS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png 424w, https://substackcdn.com/image/fetch/$s_!eCFS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png 848w, https://substackcdn.com/image/fetch/$s_!eCFS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png 1272w, https://substackcdn.com/image/fetch/$s_!eCFS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eCFS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png" width="1266" height="1480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1480,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:225351,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eCFS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png 424w, https://substackcdn.com/image/fetch/$s_!eCFS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png 848w, https://substackcdn.com/image/fetch/$s_!eCFS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png 1272w, https://substackcdn.com/image/fetch/$s_!eCFS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a14cf1-b38a-4d44-9fc3-f6780eeae1a9_1266x1480.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Plus que le fichier de test :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TNSY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TNSY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png 424w, https://substackcdn.com/image/fetch/$s_!TNSY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png 848w, https://substackcdn.com/image/fetch/$s_!TNSY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png 1272w, https://substackcdn.com/image/fetch/$s_!TNSY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TNSY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png" width="1456" height="925" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:925,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:221161,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TNSY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png 424w, https://substackcdn.com/image/fetch/$s_!TNSY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png 848w, https://substackcdn.com/image/fetch/$s_!TNSY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png 1272w, https://substackcdn.com/image/fetch/$s_!TNSY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4066f7-8491-4beb-9add-d340bec0c4a1_1480x940.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>NB : La version originale de <code>pdftk</code> est obsol&#232;te sur Linux en raison de sa d&#233;pendance &#224; GCJ et a &#233;t&#233; retir&#233;e de plusieurs d&#233;p&#244;ts. En alternative, <code>pdftk-java</code>, une r&#233;&#233;criture en Java, est activement maintenu et compatible avec les distributions modernes.<br><br>Tada &#128640; <br><br>Quelques resources :</p><ul><li><p>La gem <a href="https://github.com/jkraemer/pdf-forms#fdfxfdf-creation">pdf-forms</a> o&#249; l'on peut trouver quelques calls &#224; faire &#224; <code>pdftk</code></p></li><li><p><a href="https://www.sejda.com">Sejda</a> pour cr&#233;er/nommer des champs et se passer de Adobe Acrobat</p></li></ul><p>&#8212; <em>Stanislas</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪📖 Nos hashes éparpillés partout dans le code et comment on a mis de l'ordre]]></title><description><![CDATA[Au programme aujourd&#8217;hui :]]></description><link>https://www.rubybiscuit.fr/p/encapsule-tes-dictionnaires-dans</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/encapsule-tes-dictionnaires-dans</guid><pubDate>Wed, 08 Jan 2025 15:02:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c43404ea-44e8-4685-be8a-db6826365782_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Au programme aujourd&#8217;hui :</strong></p><ul><li><p><strong>Encapsule tes dictionnaires dans des classes d&#233;di&#233;es </strong><em>par M&#233;lanie</em></p></li></ul><p><em>Temps de lecture :<strong> 5 minutes</strong></em></p><div><hr></div><p>Hello les petits Biscuits ! <strong>Bonne et heureuse ann&#233;e &#224; tous, prenez soins de vous</strong> &#10084;&#65039;</p><p>Bienvenue sur la 29&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 546 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour <strong>recruter des devs Ruby</strong> !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w5Nb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" width="1456" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture</p><div><hr></div><p>Dans les applications Rails, il est courant de travailler avec des API&#8239;: que &#231;a soit pour cr&#233;er des documents &#224; signer, int&#233;grer un prestataire de paiement, envoyer des informations &#224; un site vitrine ou encore utiliser l&#8217;API de Google Maps pour la g&#233;olocalisation. Dans la plupart des cas, cela implique de transmettre &#224; ces API des <em>hashes</em> (dictionnaires) de param&#232;tres, plus ou moins longs. Chaque valeur de ces dictionnaires doit imp&#233;rativement respecter le format attendu par l&#8217;API, sans quoi une erreur est vite arriv&#233;e.</p><p>Chez Capsens, nous avons r&#233;cemment d&#233;cid&#233; d&#8217;am&#233;liorer notre mani&#232;re de g&#233;rer ces dictionnaires pour r&#233;duire des erreurs fr&#233;quentes de formatage. Comment&#8239;? En les encapsulants dans des fichiers d&#233;di&#233;s.</p><p>Cette pratique, en apparence anodine, rend le code plus lisible, plus r&#233;utilisable et surtout plus facilement testable. Dans cet article, je vais vous pr&#233;senter notre m&#233;thode et tenter de vous convaincre de l&#8217;adopter vous aussi.</p><p>Pour illustrer mon propos, nous allons prendre comme exemple un service qui cr&#233;e un document au format PDF &#224; l'aide de l'API <a href="https://www.doclift.io/">Doclift</a>. Ce document correspond &#224; un contrat g&#233;n&#233;r&#233; apr&#232;s une souscription financi&#232;re &#224; un projet.</p><p></p><blockquote><p><strong>Doclift</strong> est une API qui permet la g&#233;n&#233;ration dynamique de vos documents PDF. <br>Vous cr&#233;ez vos templates de document directement sur l'app Doclift (enti&#232;rement cod&#233;e en RoR), vous y ajoutez des variables et via l'API vous envoyez le contenu des variables. Pratique ! <br>Vous trouverez son fonctionnement d&#233;taill&#233; ici : <a href="https://www.doclift.io/how-it-works">https://www.doclift.io/how-it-works</a></p></blockquote><p></p><p>Voici la V1 de notre service :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fXXi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fXXi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png 424w, https://substackcdn.com/image/fetch/$s_!fXXi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png 848w, https://substackcdn.com/image/fetch/$s_!fXXi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png 1272w, https://substackcdn.com/image/fetch/$s_!fXXi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fXXi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png" width="1456" height="3702" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3702,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1143699,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fXXi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png 424w, https://substackcdn.com/image/fetch/$s_!fXXi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png 848w, https://substackcdn.com/image/fetch/$s_!fXXi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png 1272w, https://substackcdn.com/image/fetch/$s_!fXXi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1939a387-6a39-4842-b736-f4356f8b5ac0_1814x4612.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ici on constate donc qu'il y a beaucoup de param&#232;tre &#224; envoyer pour la g&#233;n&#233;ration de notre document. Le format des variables &#233;tant pr&#233;-d&#233;fini dans Doclift lors de la cr&#233;ation du template, il est essentiel de le respecter &#224; la lettre avant d'envoyer le dictionnaire. On retrouve certaines m&#233;thodes sp&#233;cifiques au formatage des donn&#233;es, qui ne sont utiles que dans ce contexte.</p><p></p><p>Les tests associ&#233;s :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5dW0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5dW0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png 424w, https://substackcdn.com/image/fetch/$s_!5dW0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png 848w, https://substackcdn.com/image/fetch/$s_!5dW0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png 1272w, https://substackcdn.com/image/fetch/$s_!5dW0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5dW0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png" width="1456" height="1420" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1420,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:352609,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5dW0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png 424w, https://substackcdn.com/image/fetch/$s_!5dW0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png 848w, https://substackcdn.com/image/fetch/$s_!5dW0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png 1272w, https://substackcdn.com/image/fetch/$s_!5dW0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f3a8021-fb85-42ac-95d8-6169d2120a13_1628x1588.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Inconv&#233;nient d'avoir son dictionnaire directement dans son service :</p><ul><li><p>Trop de responsabilit&#233;s pour le service</p></li><li><p>On ne teste pas les dictionnaires alors qu'ils sont la cause de bcp d'erreurs. Erreurs qui peuvent &#234;tre critiques en fonction du moment o&#249; se fait la g&#233;n&#233;ration du document. Au milieu d'un flow de souscription dans notre cas.</p></li><li><p>Dictionnaire non r&#233;utilisable</p></li></ul><p>Bien s&#251;r, nous pourrions am&#233;liorer nos tests et tester davantage notre dictionnaire ici, mais cela alourdirait les tests de notre service, tout comme la cr&#233;ation du hash alourdit d&#233;j&#224; ce dernier. Cela ne r&#233;soudrait pas non plus le probl&#232;me de r&#233;utilisabilit&#233;. Si demain nous devons g&#233;rer un second template de contrat avec seulement quelques variantes mais des param&#232;tres globalement similaires, il serait bien plus int&#233;ressant de ne pas tout r&#233;&#233;crire.</p><p></p><h4>Alors comment avons nous am&#233;lior&#233; notre code</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F91F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F91F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png 424w, https://substackcdn.com/image/fetch/$s_!F91F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png 848w, https://substackcdn.com/image/fetch/$s_!F91F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png 1272w, https://substackcdn.com/image/fetch/$s_!F91F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!F91F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png" width="1426" height="2344" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2344,&quot;width&quot;:1426,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:411096,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!F91F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png 424w, https://substackcdn.com/image/fetch/$s_!F91F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png 848w, https://substackcdn.com/image/fetch/$s_!F91F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png 1272w, https://substackcdn.com/image/fetch/$s_!F91F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F98886933-7414-482c-b2bd-1ea7afc98b95_1426x2344.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Vous remarquerez en particulier :</p><pre><code><code>Doclift::SubscriptionDictionary.new(</code>
<code>  @subscription</code>
<code>).to_h</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JK-K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JK-K!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png 424w, https://substackcdn.com/image/fetch/$s_!JK-K!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png 848w, https://substackcdn.com/image/fetch/$s_!JK-K!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png 1272w, https://substackcdn.com/image/fetch/$s_!JK-K!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JK-K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png" width="1006" height="1012" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1012,&quot;width&quot;:1006,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:143493,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JK-K!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png 424w, https://substackcdn.com/image/fetch/$s_!JK-K!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png 848w, https://substackcdn.com/image/fetch/$s_!JK-K!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png 1272w, https://substackcdn.com/image/fetch/$s_!JK-K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8cccb6-e024-48e0-bfd1-9733d702fdff_1006x1012.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YWhq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YWhq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png 424w, https://substackcdn.com/image/fetch/$s_!YWhq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png 848w, https://substackcdn.com/image/fetch/$s_!YWhq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png 1272w, https://substackcdn.com/image/fetch/$s_!YWhq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YWhq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png" width="1456" height="2992" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2992,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:782130,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YWhq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png 424w, https://substackcdn.com/image/fetch/$s_!YWhq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png 848w, https://substackcdn.com/image/fetch/$s_!YWhq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png 1272w, https://substackcdn.com/image/fetch/$s_!YWhq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1a927f2-91f8-4870-9241-d6804e408f52_1526x3136.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Les tests :</p><ul><li><p>Ceux du service pourraient rester les m&#234;mes</p></li><li><p>Test du dictionnaire :</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XMQF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XMQF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png 424w, https://substackcdn.com/image/fetch/$s_!XMQF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png 848w, https://substackcdn.com/image/fetch/$s_!XMQF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png 1272w, https://substackcdn.com/image/fetch/$s_!XMQF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XMQF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png" width="1456" height="3613" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3613,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:960851,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XMQF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png 424w, https://substackcdn.com/image/fetch/$s_!XMQF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png 848w, https://substackcdn.com/image/fetch/$s_!XMQF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png 1272w, https://substackcdn.com/image/fetch/$s_!XMQF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7a6db26-6c68-492d-9141-f2ed602dd698_1728x4288.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Il est maintenant facile de tester son dictionnaire et en prime, avec diff&#233;rents contextes.</p><p>Nous avons donc une solution :</p><ul><li><p>Facilement testable et maintenable</p></li><li><p>Qui all&#233;ge le service</p></li><li><p>R&#233;utilisable</p></li></ul><p>Bien s&#251;r si vous utilisez une gem comme Draper pour avoir des d&#233;corateurs sur votre application, il est tout &#224; fait possible de cr&#233;er des d&#233;corateurs plut&#244;t que des dictionnaires !</p><p></p><p>Au mois prochain !</p><p>&#8212; <em>M&#233;lanie</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🚓 3 techniques qu’on met en place pour que nos webhooks ne soient pas vulnérables]]></title><description><![CDATA[Temps de lecture : 5 minutes]]></description><link>https://www.rubybiscuit.fr/p/securiser-vos-webhooks-en-ruby-on</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/securiser-vos-webhooks-en-ruby-on</guid><pubDate>Wed, 04 Dec 2024 15:03:20 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/05c1fe84-2ab4-4295-bbea-f3563e11dea2_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Au programme aujourd&#8217;hui :</strong></p><ul><li><p><strong>S&#233;curiser vos Webhooks en Ruby on Rails </strong><em>par Fran&#231;ois</em></p></li></ul><p><em>Temps de lecture :<strong> 5 minutes</strong></em></p><div><hr></div><p>Hello les petits Biscuits !</p><p>Bienvenue sur la 28&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 540 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w5Nb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" width="1456" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><h2><strong>S&#233;curiser vos Webhooks en Ruby on Rails</strong></h2><p>Dans le d&#233;veloppement d&#8217;applications web, les <strong>webhooks</strong> sont un m&#233;canisme courant pour recevoir des notifications externes sur des &#233;v&#233;nements sp&#233;cifiques. Par exemple, une application peut recevoir un webhook de service tiers de signature &#233;lectronique pour l'informer lorsqu'un document a &#233;t&#233; sign&#233;. Cependant, sans mesures de s&#233;curit&#233; appropri&#233;es, les webhooks peuvent &#234;tre exploit&#233;s, exposant ainsi votre application &#224; des risques de s&#233;curit&#233;.</p><p>Dans cet article, nous allons passer en revue un certain nombre de techniques disponibles pour s&#233;curiser les webhooks. Ensuite nous verrons une mise en pratique avec les webhooks de Dropbox Sign. Nous verrons comment mettre en pratique trois techniques pour s&#233;curiser les webhooks dans une application <strong>Ruby on Rails </strong>: la <strong>v&#233;rification des adresses IP</strong>, l'utilisation d'un secret partag&#233; (<strong>HMAC</strong>), et les <strong>strong parameters</strong> pour filtrer les donn&#233;es entrantes. Ces pratiques garantiront que seules les requ&#234;tes authentiques et s&#233;curis&#233;es seront trait&#233;es par votre application. Enfin nous verrons comment tester ces 3 mesures de s&#233;curit&#233; avec Rspec.</p><h2>Comprendre les M&#233;thodes de S&#233;curisation des Webhooks</h2><p>Avant d'entrer dans l'impl&#233;mentation, voyons quelques m&#233;thodes disponibles pour la s&#233;curisation des webhooks :</p><ol><li><p><strong>Appeler le service</strong> : M&#234;me lorsqu'ils sont s&#233;curis&#233;s, les webhooks ne sont pas adapt&#233;s pour transmettre des informations sensibles. Une fa&#231;on de s&#233;curis&#233; l&#8217;utilisation des webhooks est de seulement les utiliser pour &#234;tre inform&#233; d&#8217;un &#233;v&#233;nement pour une ressource et ensuite d&#8217;appeler le service externe via une API pour connaitre l&#8217;&#233;tat de cette ressource. (voir exemple dans la section suivante)</p></li><li><p><strong>V&#233;rification des IPs</strong> : Restreindre l&#8217;acc&#232;s aux webhooks uniquement &#224; certaines adresses IP. Cela permet de s&#8217;assurer que les requ&#234;tes proviennent d&#8217;une source autoris&#233;e, comme un service tiers de confiance (Dropbox Sign dans cet exemple).</p></li><li><p><strong>V&#233;rification du Secret (HMAC)</strong> : Un secret partag&#233; ou un HMAC (Hash-based Message Authentication Code) permet de valider que la requ&#234;te n'a pas &#233;t&#233; alt&#233;r&#233;e et qu'elle provient bien de la source pr&#233;vue.</p></li><li><p><strong>Strong Parameters</strong> : Filtrer les donn&#233;es entrantes pour s'assurer qu'elles correspondent aux attentes et &#233;viter les injections de donn&#233;es non d&#233;sir&#233;es ou malveillantes.</p></li><li><p><strong>Timestamp pour pr&#233;venir les attaques par rejeu (Replay Attacks)</strong> : En incluant un timestamp dans la requ&#234;te du webhook, vous pouvez v&#233;rifier que l'&#233;v&#233;nement n'est pas ancien et qu'il n'a pas &#233;t&#233; rejou&#233;. Si l'horodatage d&#233;passe un certain seuil (par exemple, 5 minutes), la requ&#234;te est rejet&#233;e.</p></li><li><p><strong>Chiffrement des donn&#233;es envoy&#233;es</strong> : Chiffrer les donn&#233;es transmises via les webhooks, notamment lorsqu'elles contiennent des informations sensibles, garantit que seules les parties autoris&#233;es peuvent les lire. Cela peut &#234;tre r&#233;alis&#233; avec HTTPS ou en chiffrant manuellement les payloads.</p></li><li><p><strong>Utiliser HTTPS et la v&#233;rification SSL </strong>: GitHub v&#233;rifie les certificats SSL lors de la transmission des webhooks et il est fortement recommand&#233; de laisser cette v&#233;rification activ&#233;e. Cela attenue les risques d&#8217;&#233;coutes clandestines (eavesdropping) et les attaques de l'intercepteur (man-in-the-middle MITM), o&#249; un attaquant peut intercepter et modifier la donn&#233;e avant de la transmettre.</p></li></ol><p>Voir plus sur <a href="https://webhooks.fyi/security/intro">Introduction to Webhook Security - Docs</a></p><h2>Appeler le service tiers apr&#232;s reception du webhook</h2><p>Comme mentionn&#233; dans la section pr&#233;c&#233;dente, les webhooks ne sont pas la m&#233;thode de communication la plus s&#233;curis&#233;e et les donn&#233;es transf&#233;r&#233;es sont sensibles (exemple les donn&#233;es bancaires). Par cons&#233;quent, certains fournisseurs de service comme <a href="https://docs.swan.io/developers/using-api/webhooks/">Swan</a> n'utilisent pas de webhooks pour envoyer des informations sensibles qui n&#233;cessitent une authentification pour &#234;tre affich&#233;es. Le webhook inclut uniquement une notification indiquant qu'un &#233;v&#233;nement s'est produit et une id de ressource afin de pouvoir la retrouver. Il faut ensuite utiliser l'API pour obtenir les informations. On peut visualiser cela dans le graphique ci dessous :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KIdu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KIdu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png 424w, https://substackcdn.com/image/fetch/$s_!KIdu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png 848w, https://substackcdn.com/image/fetch/$s_!KIdu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png 1272w, https://substackcdn.com/image/fetch/$s_!KIdu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KIdu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png" width="554" height="440" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:440,&quot;width&quot;:554,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56334,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KIdu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png 424w, https://substackcdn.com/image/fetch/$s_!KIdu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png 848w, https://substackcdn.com/image/fetch/$s_!KIdu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png 1272w, https://substackcdn.com/image/fetch/$s_!KIdu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56ce2cb8-8286-4a92-b6b6-5473ab9fdca1_554x440.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>S&#233;curisation gr&#226;ce aux fonctionnalit&#233;s fournies</h2><p>Tous les fournisseurs de service tiers mettant &#224; disposition des webhooks ne fournissent pas de mesures de s&#233;curit&#233;, mais beaucoup le font. Les contr&#244;les de s&#233;curit&#233; mis &#224; disposition par le fournisseur de webhook permettent de valider l'authenticit&#233; des &#233;changes et &#224; emp&#234;cher leur falsification. Cependant, la plupart des fournisseurs n'exigent pas de la part des applications consommants leur service qu'elles utilisent la s&#233;curit&#233; qu'ils fournissent. <strong>C'est aux d&#233;veloppeurs de lire la documentation du webhook du fournisseur pour savoir quelles options sont disponibles, les comprendre, puis les mettre en &#339;uvre</strong>.</p><h2>Impl&#233;mentation en Ruby on Rails</h2><p>Dans cet exemple, nous allons impl&#233;menter un contr&#244;leur webhook pour <strong>Dropbox Sign</strong>, avec les trois couches de s&#233;curit&#233; suivantes : la v&#233;rification d'IP, le HMAC, et les strong parameters.</p><h3>Code du Contr&#244;leur</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!saKh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!saKh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png 424w, https://substackcdn.com/image/fetch/$s_!saKh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png 848w, https://substackcdn.com/image/fetch/$s_!saKh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png 1272w, https://substackcdn.com/image/fetch/$s_!saKh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!saKh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png" width="1456" height="2209" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2209,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:641418,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!saKh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png 424w, https://substackcdn.com/image/fetch/$s_!saKh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png 848w, https://substackcdn.com/image/fetch/$s_!saKh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png 1272w, https://substackcdn.com/image/fetch/$s_!saKh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcde005d8-7a19-4369-97b9-4bc9a6c751e1_1830x2776.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Explications :</h3><ul><li><p><strong>V&#233;rification d'IP</strong> : Le filtre <code>verify_ip_address</code> s'assure que la requ&#234;te provient bien des IPs autoris&#233;es d&#233;finies dans la constante <code>ALLOWED_IPS</code>.</p></li><li><p><strong>V&#233;rification HMAC</strong> : La m&#233;thode <code>verify_secret</code> utilise un secret (cl&#233; API) pour v&#233;rifier la signature de la requ&#234;te. Cela garantit que la requ&#234;te provient bien de Dropbox Sign et qu'elle n'a pas &#233;t&#233; modifi&#233;e.</p></li><li><p><strong>Strong Parameters</strong> : Nous utilisons <code>webhook_payload</code> pour filtrer et limiter les donn&#233;es aux seuls param&#232;tres attendus, ce qui emp&#234;che l'injection de donn&#233;es non d&#233;sir&#233;es.</p></li></ul><p>Voir la documentation de Dropbox Sign pour plus de d&#233;tail : <a href="https://developers.hellosign.com/docs/events/walkthrough/#securing-your-callback-handler">Events Walkthrough | Dropbox Sign for Developers</a></p><h2>Rspec tests</h2><p>Il est important de tester les v&#233;rifications, notamment pour s&#8217;assurer que seules les IPs autoris&#233;es et les requ&#234;tes valides sont accept&#233;es.</p><p>Voici un exemple de tests :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3uZc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3uZc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png 424w, https://substackcdn.com/image/fetch/$s_!3uZc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png 848w, https://substackcdn.com/image/fetch/$s_!3uZc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png 1272w, https://substackcdn.com/image/fetch/$s_!3uZc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3uZc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png" width="1456" height="2420" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2420,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:711866,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3uZc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png 424w, https://substackcdn.com/image/fetch/$s_!3uZc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png 848w, https://substackcdn.com/image/fetch/$s_!3uZc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png 1272w, https://substackcdn.com/image/fetch/$s_!3uZc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93251e41-4c0c-4845-8669-cc9c2d019654_1930x3208.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Ce que fait ce test :</h3><ul><li><p>Il simule lorsque tout va bien.</p></li><li><p>Il simule des requ&#234;tes venant d&#8217;une IP autoris&#233;e et d&#8217;une IP non autoris&#233;e.</p><ul><li><p>Si l'IP est autoris&#233;e, le webhook est trait&#233; avec succ&#232;s.</p></li><li><p>Si l'IP n'est pas autoris&#233;e, une r&#233;ponse "not found" est renvoy&#233;e, emp&#234;chant la requ&#234;te d'&#234;tre trait&#233;e.</p></li></ul></li><li><p>Il simule des requ&#234;tes avec un secret valide et invalide.</p><ul><li><p>Si le secret est valide, le webhook est trait&#233; avec succ&#232;s.</p></li><li><p>Si le secret n&#8217;est pas valide, une r&#233;ponse "not found" est renvoy&#233;e, emp&#234;chant la requ&#234;te d'&#234;tre trait&#233;e.</p></li></ul></li></ul><h2>Conclusion</h2><p>La s&#233;curisation des webhooks est cruciale pour prot&#233;ger vos applications contre les attaques et les abus. En combinant la v&#233;rification des adresses IP, l&#8217;utilisation de secrets partag&#233;s et le filtrage des param&#232;tres, vous vous assurez que seules les requ&#234;tes authentiques et s&#251;res sont trait&#233;es par votre application.</p><p></p><p>Repo associ&#233; : https://github.com/francoisedumas/basic_app_esbuild</p><h2>Pour aller plus loin</h2><p>Vous trouverez sur ma chaine YouTube de nombreux sujet technique que j&#8217;explore et explique. <a href="https://www.youtube.com/@KokoriKodo">Kokori Kodo</a></p><p>&#8212; <em>Fran&#231;ois</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪☠️ PDF et JavaScript malveillant : comment on protège nos plateformes]]></title><description><![CDATA[Temps de lecture : 10 minutes]]></description><link>https://www.rubybiscuit.fr/p/en-finir-avec-les-pdf-infectes-au</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/en-finir-avec-les-pdf-infectes-au</guid><pubDate>Wed, 06 Nov 2024 15:02:24 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c727a8f0-642a-46e6-94c6-7ca63ee710f0_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128680; Cette &#233;dition est relativement longue, votre bo&#238;te mail risque de la tronquer. Je vous conseille de cliquer sur le titre ci-dessus pour l&#8217;ouvrir dans votre navigateur web. &#9757;&#65039;</em></p><p><em>Temps de lecture :<strong> 10 minutes</strong></em></p><div><hr></div><p>Hello les petits Biscuits !</p><p>Bienvenue sur la 27&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 540 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w5Nb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" width="1456" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><h2><strong>En finir avec les PDF infect&#233;s au js</strong></h2><p>En mai 2024, <a href="https://codeanlabs.com/blog/research/cve-2024-4367-arbitrary-js-execution-in-pdf-js/">une faille majeure a &#233;t&#233; d&#233;couverte dans la biblioth&#232;que PDF.js</a>, utilis&#233;e par de tr&#232;s nombreux iframes pour permettre la visualisation et la pr&#233;visualisation de fichiers PDF dans une page web. Cette faille permettait d'injecter du code javascript dans une FontMatrix qui n'&#233;tait pas cens&#233;e en accueillir et ensuite de s'assurer de son ex&#233;cution lors de l'interpr&#233;tation du fichier par un lecteur. Ce type de faille est une porte d'entr&#233;e pour des attaques de type XSS, bien qu'elle n'en permette pas une seule.</p><blockquote><p><em>Le <strong>cross-site scripting</strong> (abr&#233;g&#233; <strong>XSS</strong>) est un type de faille de s&#233;curit&#233; des sites web permettant d'injecter du contenu dans une page, provoquant ainsi des actions sur les navigateurs web visitant la page. </em>Il est par exemple possible de voler la session en r&#233;cup&#233;rant les cookies.</p></blockquote><p>Les CVE concernant l'ex&#233;cution arbitraire de javascript via des fichiers PDF dans des pages web sont nombreuses, et il y a aussi des multitudes de formes d'ex&#233;cution de javascript volontairement permises par le format PDF. Celles-ci sont g&#233;n&#233;ralement bloqu&#233;es par le navigateur mais il y a des exceptions.</p><p>Aujourd'hui nous allons apprendre comment nous en d&#233;barrasser. Apr&#232;s avoir lu cet article, vous saurez comment impl&#233;menter un sanitizer en ruby qui vous prot&#232;ge durablement de la CVE de mai 2024 et d'autres types d'injections au js. Vous serez &#233;galement solidement arm&#233;s pour r&#233;agir efficacement et rapidement &#224; la prochaine CVE de ce style et capables de :</p><ul><li><p>Comprendre sur quoi se base la CVE</p></li><li><p>Adapter votre sanitizer</p></li><li><p>Cr&#233;er vos propres fichiers infect&#233;s pour le tester</p></li></ul><h1>I - Introduction, les actions javascripts et les fontmatrix</h1><p>Entre la cr&#233;ation du format PDF en 1992 o&#249; seulement 12 types d'objets &#233;taient autoris&#233;s et sa derni&#232;re mise &#224; jour en 2009 qui en compte plus de 70, de nombreuses fonctionnalit&#233;s ont &#233;t&#233; ajout&#233;es. Et avec elles beaucoup de vuln&#233;rabilit&#233;s.</p><p>Une partie non n&#233;gligeable de ces vuln&#233;rabilit&#233;s et dangers ont &#233;t&#233; introduits en m&#234;me temps que les scripts javascript.</p><p>La plupart de ces scripts ne seront pas malveillants : widgets, liens dynamiques, actions sympathiques et vari&#233;es au clic d'un bouton ou d&#232;s l'ouverture d'un fichier : le javascript a &#233;t&#233; admis pour am&#233;liorer et enrichir l'exp&#233;rience utilisateur.</p><p>D'autres le seront probablement, c'est le cas des PDF &#233;crits pour contenir du javascript de sorte &#224; exploiter la CVE de mai 2024.</p><p>Je vais vous montrer comment &#233;crire un script ruby qui sanitise les fichiers PDF pour leur enlever les &#233;l&#233;ments javascripts l&#233;gitimes ou ill&#233;gitimes.</p><p>Mais avant il y a quelques pr&#233;-requis n&#233;cessaires :</p><ul><li><p>Une compr&#233;hension globale du format PDF</p></li><li><p>Une connaissance des emplacements et des formes que le javascript peut occuper dans un PDF</p></li><li><p>Des fichiers PDF "infect&#233;s" pour tester nos scripts</p></li></ul><p>Nous allons obtenir tout cela en cr&#233;ant ensemble et de toute pi&#232;ce un fichier PDF infect&#233; au javascript (inoffensif bien s&#251;r) !</p><h1>II - Cr&#233;ons un fichier corrompu</h1><p>Nous allons dans cette partie &#233;crire &#224; la main, de A &#224; Z, un fichier PDF. Le javascript pouvant se trouver dans de tr&#232;s nombreuses sections d'un PDF, je montrerai pour chaque section dans un premier temps la version saine et d&#233;pourvue de javascript, puis la version infect&#233;e.</p><p>Cependant, il est impossible de pr&#233;senter l'ensemble des objets li&#233;s &#224; des actions javascript en un seul article tant ceux-ci sont nombreux. <a href="https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf">La norme PDF 32000-1:2008 ou PDF 1.7</a> d&#233;crit de fa&#231;on exhaustive ces objets si cela vous int&#233;resse (page 422).</p><p>Pour chaque section, les fragments de code PDF en version saine pr&#233;sent&#233;s dans l'article, une fois assembl&#233;s, formeront un fichier PDF valide et non corrompu.<br><em>Si vous souhaitez cr&#233;er vous m&#234;me votre </em>PDF<em> infect&#233;, il faudra adapter les offsets de la table des r&#233;f&#233;rences (je vous expliquerai tout &#231;a en d&#233;tail le moment venu).</em></p><h4>Speedrun</h4><p>Si vous souhaitez speedrun la lecture de cet article, et n'en sortir qu'un script de sanitisation et des fichiers de payload pour le tester, vous pouvez sauter directement &#224; la partie suivante o&#249; vous trouverez les deux.</p><p>Je ne vous conseille cependant de ne pas faire cela, l'int&#233;r&#234;t de coder votre propre sanitizer &#233;tant de pouvoir l'adapter rapidement &#224; toute nouvelle CVE permettant une ex&#233;cution de javascript dans un pdf via des moyens d&#233;tourn&#233;s. Cela peut &#234;tre plus compliqu&#233; sans une vague compr&#233;hension du format PDF et des objets javascript.</p><p></p><h4>Le format g&#233;n&#233;ral</h4><p>Une fa&#231;on de se repr&#233;senter un fichier PDF serait de dire qu'il contient globalement 3 sections :</p><ul><li><p>L'<strong>en-t&#234;te </strong>qui annonce le format et la version (tr&#232;s court)</p></li><li><p>Le <strong>corps</strong> qui abrite le contenu (ou charge utile) du PDF</p></li><li><p>La <strong>fin de fichier</strong> qui regroupe la table des r&#233;f&#233;rences, le trailer et le marqueur de fin de fichier : des &#233;l&#233;ments utiles &#224; la bonne interpr&#233;tation du document mais n'ajoutant g&#233;n&#233;ralement pas de contenu &#224; proprement parler.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gmYV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gmYV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png 424w, https://substackcdn.com/image/fetch/$s_!gmYV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png 848w, https://substackcdn.com/image/fetch/$s_!gmYV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png 1272w, https://substackcdn.com/image/fetch/$s_!gmYV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gmYV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png" width="1275" height="938" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:938,&quot;width&quot;:1275,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:14187,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gmYV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png 424w, https://substackcdn.com/image/fetch/$s_!gmYV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png 848w, https://substackcdn.com/image/fetch/$s_!gmYV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png 1272w, https://substackcdn.com/image/fetch/$s_!gmYV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcfe8f16-3645-473d-ae8e-402b69a050ed_1275x938.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">repr&#233;sentation visuelle d&#8217;un pdf</figcaption></figure></div><p>Nous allons remplir ce guide progressivement dans cette partie.</p><h2>En t&#234;te</h2><p>Un PDF commence directement par une section d'en-t&#234;te. L'en t&#234;te pr&#233;cise le format&nbsp;: <strong>PDF</strong>, la version : <strong>1.3</strong> et quelques caract&#232;res binaires pour annoncer que le fichier sera encod&#233; en binaire.</p><p>Le fichier que nous allons &#233;crire &#233;tant constitu&#233; de tr&#232;s peu d'objets, il sera valide et lisible avec n'importe quelle version et par n'importe quel lecteur PDF (si vous souhaitez &#233;crire la version contenant du JS &#224; la main, utilisez une version r&#233;cente comme la 1.8 ou au minimum post&#233;rieure &#224; 1.2).</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LQ6e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LQ6e!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png 424w, https://substackcdn.com/image/fetch/$s_!LQ6e!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png 848w, https://substackcdn.com/image/fetch/$s_!LQ6e!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png 1272w, https://substackcdn.com/image/fetch/$s_!LQ6e!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LQ6e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png" width="1456" height="315" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:315,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15112,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LQ6e!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png 424w, https://substackcdn.com/image/fetch/$s_!LQ6e!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png 848w, https://substackcdn.com/image/fetch/$s_!LQ6e!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png 1272w, https://substackcdn.com/image/fetch/$s_!LQ6e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0f91516-6383-4bba-93dc-bb232f2568d6_1588x344.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2>Corps</h2><p>Le corps regroupe tout le contenu d'un PDF : c'est une suite d'objets indirects. Ces objets peuvent &#234;tre de types vari&#233;s. Cependant certains types d'objets sont n&#233;cessaires &#224; l'&#233;laboration d'un PDF valide minimal.</p><p>Afin de comprendre le format du corps, quelques d&#233;finitions s'imposent &#224; ce sujet.</p><p>Le corps d'un fichier PDF est constitu&#233; d'une s&#233;rie d'objets indirects.</p><blockquote><p>Les <strong>objets</strong> <strong>indirects</strong> sont compos&#233;s d'un num&#233;ro d'objet (un identifiant), d'un num&#233;ro de g&#233;n&#233;ration (qui commence &#224; 0 et s'incr&#233;mente si l'objet est dupliqu&#233;).</p><p>Puis <strong>obj </strong>[Contenu objet] <strong>endobj</strong></p></blockquote><pre><code><code>1 0 obj</code>
<code>[CONTENT]</code>
<code>endobj</code></code></pre><p>Un objet indirect peut &#234;tre vu comme un contenant pour un ou plusieurs objet directs.</p><blockquote><p>Les <strong>objets directs</strong> (ou objets) sont des structures de donn&#233;es basiques, qui peuvent souvent &#234;tre directement traduites vers un type d'objet primitif en Ruby (tableaux, bool&#233;ens, dictionnaires, cha&#238;ne de caract&#232;res, entiers, objet nul, ...).</p></blockquote><p></p><p>La plupart des objets directs de ce document seront des dictionnaires.</p><blockquote><p>Les <strong>dictionnaires </strong>sont des s&#233;quences de cl&#233;s et valeurs entour&#233;s de <strong>&lt;&lt;</strong> et <strong>&gt;&gt;</strong>. Chaque cl&#233;s type sa valeur. Les cl&#233;s sont toutes des <em>names objects</em> (au format <strong>/Name</strong>)<em>.</em></p></blockquote><p></p><p>Les &#233;l&#233;ments simples que vous voyez &#224; l'&#233;cran lorsque vous visualisez un PDF sont en fait constitu&#233;s d'une multitudes d'objets qui sont imbriqu&#233;s et/ou qui se r&#233;f&#233;rencent. Les dictionnaires par exemple sont souvent imbriqu&#233;s &#224; de nombreux niveaux.</p><blockquote><p>Les objets indirects peuvent se <strong>r&#233;f&#233;rencer </strong>les uns les autres. Une <strong>r&#233;f&#233;rence</strong> est un type d'objet. Elle se compose des num&#233;ros d'objet et de g&#233;n&#233;ration de l'objet r&#233;f&#233;renc&#233; suivis du caract&#232;re <strong>R</strong>.</p></blockquote><p>Ainsi, un objet indirect d&#233;fini de la fa&#231;on suivante :</p><pre><code><code>2 0 obj</code>
<code>[Contenu]</code>
<code>endobj</code></code></pre><p>Pourra &#234;tre r&#233;f&#233;renc&#233; (avant ou apr&#232;s sa d&#233;finition dans le document) par :<strong> 2 0 R</strong>.</p><p></p><p><strong>Ne pas confondre objet direct et indirect</strong></p><p>Dans un fichier PDF, les objets peuvent &#234;tre repr&#233;sent&#233;s de mani&#232;re <em>directe</em> ou <em>indirecte</em>. Un objet direct est int&#233;gr&#233; directement dans le document et est souvent trouv&#233; comme valeur d'une cl&#233; dans un dictionnaire ou comme &#233;l&#233;ment dans un tableau. Il est accessible imm&#233;diatement sans r&#233;f&#233;rence externe. En revanche, un objet indirect est r&#233;f&#233;renc&#233; par un identifiant unique (compos&#233; d'un num&#233;ro d'objet et d'un num&#233;ro de g&#233;n&#233;ration), ce qui permet au lecteur PDF de retrouver sa valeur en consultant d&#8217;autres parties du fichier. Les objets indirects facilitent la r&#233;utilisation et l'organisation des donn&#233;es dans un PDF en permettant de pointer vers un objet unique depuis plusieurs endroits.</p><p>Passons &#224; la pratique.</p><p></p><h4>Le catalogue</h4><p>Le premier objet indirect contenu dans le corps est ici le catalogue. C'est l'objet racine du document. C'est un dictionnaire qui r&#233;f&#233;rence l'objet liste des pages.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Tlo9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Tlo9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png 424w, https://substackcdn.com/image/fetch/$s_!Tlo9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png 848w, https://substackcdn.com/image/fetch/$s_!Tlo9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png 1272w, https://substackcdn.com/image/fetch/$s_!Tlo9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Tlo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png" width="1456" height="712" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:712,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48939,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Tlo9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png 424w, https://substackcdn.com/image/fetch/$s_!Tlo9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png 848w, https://substackcdn.com/image/fetch/$s_!Tlo9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png 1272w, https://substackcdn.com/image/fetch/$s_!Tlo9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c57a1b4-9cdf-4a51-99cb-f75fef24962b_1588x777.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Il peut contenir d'autres types d'objets. Il peut notamment contenir des actions javascript l&#233;gitimes ou non.</p><p></p><h4>Les actions javascript</h4><blockquote><p>Une <strong>action javascript</strong> est en r&#233;alit&#233; une s&#233;rie d'objets et peut prendre de multiples formes. Ici, <strong>action javascript</strong> d&#233;signe les diff&#233;rents types d'objets qui r&#233;f&#233;rencent une ex&#233;cution de javascript (c&#244;t&#233; navigateur) conforme au format PDF et &#224; son contenu. Ils correspondent &#224; des utilisations non d&#233;tourn&#233;es de diff&#233;rents types d'objets. Ces objets peuvent &#234;tre imbriqu&#233;s.</p></blockquote><p></p><p>Elles peuvent &#234;tre contenues dans un dictionnaire de type<strong> /Action </strong>ou <strong>/OpenAction</strong>. La premi&#232;re est g&#233;n&#233;ralement attach&#233;e &#224; une interaction de l'utilisateur comme un clic, la seconde s'ex&#233;cute d&#232;s l'ouverture du document (pratique).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iPUY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iPUY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png 424w, https://substackcdn.com/image/fetch/$s_!iPUY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png 848w, https://substackcdn.com/image/fetch/$s_!iPUY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png 1272w, https://substackcdn.com/image/fetch/$s_!iPUY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iPUY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png" width="1456" height="1025" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1025,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55244,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iPUY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png 424w, https://substackcdn.com/image/fetch/$s_!iPUY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png 848w, https://substackcdn.com/image/fetch/$s_!iPUY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png 1272w, https://substackcdn.com/image/fetch/$s_!iPUY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e3e10b1-6408-49e3-a1a9-141492aa6a6d_1654x1164.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ces objets peuvent aussi &#234;tre d&#233;finis ind&#233;pendamment, les deux premiers sont alors simplement des dictionnaires qui r&#233;f&#233;rencent les suivants. L'objet /JS est contenu dans un objet indirect et contient directement le code &#224; ex&#233;cuter.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t2oh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t2oh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png 424w, https://substackcdn.com/image/fetch/$s_!t2oh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png 848w, https://substackcdn.com/image/fetch/$s_!t2oh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png 1272w, https://substackcdn.com/image/fetch/$s_!t2oh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t2oh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png" width="787" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:787,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:20651,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t2oh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png 424w, https://substackcdn.com/image/fetch/$s_!t2oh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png 848w, https://substackcdn.com/image/fetch/$s_!t2oh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png 1272w, https://substackcdn.com/image/fetch/$s_!t2oh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5984ac8a-3d2e-41f5-956c-e87d30bc5420_787x736.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Retour au catalogue</h4><p>Le catalogue peut contenir des actions javascript.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tB4P!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tB4P!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png 424w, https://substackcdn.com/image/fetch/$s_!tB4P!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png 848w, https://substackcdn.com/image/fetch/$s_!tB4P!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png 1272w, https://substackcdn.com/image/fetch/$s_!tB4P!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tB4P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png" width="1456" height="726" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:726,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58275,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tB4P!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png 424w, https://substackcdn.com/image/fetch/$s_!tB4P!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png 848w, https://substackcdn.com/image/fetch/$s_!tB4P!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png 1272w, https://substackcdn.com/image/fetch/$s_!tB4P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056fb562-4df9-4928-a70d-379f16769b6f_1588x792.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ou les r&#233;f&#233;rencer.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QajS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QajS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png 424w, https://substackcdn.com/image/fetch/$s_!QajS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png 848w, https://substackcdn.com/image/fetch/$s_!QajS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png 1272w, https://substackcdn.com/image/fetch/$s_!QajS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QajS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png" width="1456" height="634" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:634,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58493,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QajS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png 424w, https://substackcdn.com/image/fetch/$s_!QajS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png 848w, https://substackcdn.com/image/fetch/$s_!QajS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png 1272w, https://substackcdn.com/image/fetch/$s_!QajS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08d7a774-81d0-43e4-8778-55b12e265546_1654x720.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Liste des pages</h4><p>La <strong>liste des pages</strong> est un dictionnaire qui liste les r&#233;f&#233;rences des pages (ses enfants) dans un tableau. Ici le tableau contient une seule entr&#233;e puisque le PDF que l'on &#233;crit contient une seule page.</p><blockquote><p>Les <strong>tableaux</strong> sont ouverts et ferm&#233;s par [ et ], les diff&#233;rentes entr&#233;es sont s&#233;par&#233;es par des espaces.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WSp1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WSp1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png 424w, https://substackcdn.com/image/fetch/$s_!WSp1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png 848w, https://substackcdn.com/image/fetch/$s_!WSp1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png 1272w, https://substackcdn.com/image/fetch/$s_!WSp1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WSp1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png" width="1456" height="555" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:555,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27257,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WSp1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png 424w, https://substackcdn.com/image/fetch/$s_!WSp1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png 848w, https://substackcdn.com/image/fetch/$s_!WSp1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png 1272w, https://substackcdn.com/image/fetch/$s_!WSp1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0d37106-7a8b-495a-8d81-f36d130d0b6c_1869x712.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>S'il y avait plusieurs pages, les r&#233;f&#233;rences seraient s&#233;par&#233;es par un simple espace : <strong>[ 3 0 R 4 0 R ]</strong>...</p><p></p><h4>Page 1</h4><p>Chaque page d&#233;finit dans un dictionnaire Page les ressources n&#233;cessaires &#224; son affichage : une police. La page r&#233;f&#233;rence aussi son contenu : ici l'objet identifi&#233; par la r&#233;f&#233;rence 4 0 R.</p><ul><li><p>On peut y trouver des scripts ou des r&#233;f&#233;rences &#224; des scripts</p></li><li><p>Les polices peuvent &#234;tre enti&#232;rement d&#233;finies dans le PDF ou utiliser celles install&#233;es sur les ordinateurs (dans le premier cas, cela pourrait causer des probl&#232;mes d'affichage selon le lecteur)</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-evb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-evb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png 424w, https://substackcdn.com/image/fetch/$s_!-evb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png 848w, https://substackcdn.com/image/fetch/$s_!-evb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png 1272w, https://substackcdn.com/image/fetch/$s_!-evb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-evb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png" width="1456" height="1162" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1162,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:99172,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-evb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png 424w, https://substackcdn.com/image/fetch/$s_!-evb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png 848w, https://substackcdn.com/image/fetch/$s_!-evb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png 1272w, https://substackcdn.com/image/fetch/$s_!-evb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c26c1f2-c3b0-4514-b842-9f7f9ff7bf68_1654x1320.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Le stream</h4><p>Dans un fichier PDF, un <strong>stream</strong> est un type d&#8217;objet utilis&#233; pour stocker des donn&#233;es volumineuses ou complexes, comme des images, du texte format&#233; ou des informations graphiques. Il est d&#233;limit&#233; par les mots cl&#233;s <code>stream</code> et <code>endstream</code>. Les streams permettent d&#8217;inclure des contenus plus longs ou compress&#233;s dans le fichier PDF, rendant ce type de donn&#233;es plus facile &#224; g&#233;rer et &#224; stocker de mani&#232;re efficace.</p><p>Un stream commence g&#233;n&#233;ralement par une d&#233;claration de ses propri&#233;t&#233;s dans un dictionnaire pr&#233;c&#233;dant le mot <code>stream</code>. Ce dictionnaire contient des informations essentielles, comme la longueur du stream (en octets) et, le cas &#233;ch&#233;ant, la m&#233;thode de compression utilis&#233;e (par exemple, <em>FlateDecode</em> pour une compression zlib/deflate).</p><p>Voici un exemple simplifi&#233; d&#8217;un objet stream dans un fichier PDF (ici, aucune compression n'a &#233;t&#233; appliqu&#233;e) :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6vpz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6vpz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png 424w, https://substackcdn.com/image/fetch/$s_!6vpz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png 848w, https://substackcdn.com/image/fetch/$s_!6vpz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png 1272w, https://substackcdn.com/image/fetch/$s_!6vpz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6vpz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png" width="1456" height="1162" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1162,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:68601,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6vpz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png 424w, https://substackcdn.com/image/fetch/$s_!6vpz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png 848w, https://substackcdn.com/image/fetch/$s_!6vpz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png 1272w, https://substackcdn.com/image/fetch/$s_!6vpz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4e9a270-198c-469b-8759-0a985acbd2b6_1654x1320.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Cet objet stream contient simplement du texte format&#233; &#224; afficher.</p><p>Le texte est ins&#233;r&#233; entre les tags BT (Begin Text) et ET (End Text).</p><p>La premi&#232;re lign&#233;e d&#233;finit la police : F1 (d&#233;finie plus t&#244;t) et la taille : 100 gr&#226;ce au tag Tf (text font).</p><p>La deuxi&#232;me ligne indique les coordonn&#233;es gr&#226;ce au tag de dessin Td (text draw).</p><p>La derni&#232;re affiche le texte gr&#226;ce &#224; l'op&#233;rateur Tj (show text).</p><p></p><h4>Les FontMatrix</h4><p>Une <strong>FontMatrix</strong> est une matrice de transformation associ&#233;e aux polices de caract&#232;res. Elle permet d'appliquer des transformations g&#233;om&#233;triques comme la mise &#224; l'&#233;chelle, la rotation, la translation ou le cisaillement aux glyphes (caract&#232;res) d'une police avant leur affichage. Dans une <strong>FontMatrix</strong>, six valeurs num&#233;riques d&#233;finissent ces transformations, offrant un contr&#244;le pr&#233;cis sur la pr&#233;sentation des textes.</p><p>Dans un fichier PDF, les FontMatrix sont g&#233;n&#233;ralement des entr&#233;es dans un dictionnaire qui d&#233;finit une police et un formatage.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!I0-u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!I0-u!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png 424w, https://substackcdn.com/image/fetch/$s_!I0-u!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png 848w, https://substackcdn.com/image/fetch/$s_!I0-u!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png 1272w, https://substackcdn.com/image/fetch/$s_!I0-u!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!I0-u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png" width="804" height="292" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:292,&quot;width&quot;:804,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:8221,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!I0-u!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png 424w, https://substackcdn.com/image/fetch/$s_!I0-u!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png 848w, https://substackcdn.com/image/fetch/$s_!I0-u!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png 1272w, https://substackcdn.com/image/fetch/$s_!I0-u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441e19b1-99b6-4c9f-87a9-c44657a28d99_804x292.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>La CVE-2024-4367</h4><p>La faille d&#233;couverte en mai 2024 exploite une faiblesse dans la gestion des FontMatrix par la biblioth&#232;que PDF.js, permettant l&#8217;injection et l'ex&#233;cution de code JavaScript malveillant.</p><p>Les valeurs contenues dans la FontMatrix &#233;taient interpr&#233;t&#233;es par PDF.js et &#233;valu&#233;es pour afficher correctement la police. Si elles &#233;taient remplac&#233;es par du code javascript, celui-ci &#233;tait donc &#233;valu&#233;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s7ht!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s7ht!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png 424w, https://substackcdn.com/image/fetch/$s_!s7ht!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png 848w, https://substackcdn.com/image/fetch/$s_!s7ht!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png 1272w, https://substackcdn.com/image/fetch/$s_!s7ht!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s7ht!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png" width="804" height="335" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/686286e5-046c-4092-a173-bd65fcb4af22_804x335.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:335,&quot;width&quot;:804,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:11896,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s7ht!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png 424w, https://substackcdn.com/image/fetch/$s_!s7ht!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png 848w, https://substackcdn.com/image/fetch/$s_!s7ht!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png 1272w, https://substackcdn.com/image/fetch/$s_!s7ht!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F686286e5-046c-4092-a173-bd65fcb4af22_804x335.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ce cas de figure sera trait&#233; de fa&#231;on l&#233;g&#232;rement diff&#233;rente par le sanitizer : contrairement aux autres objets &#233;voqu&#233;s permettant des actions javascript, les FontMatrix n'ont en th&#233;orie, aucun rapport avec l'ex&#233;cution de javascript. Elles peuvent donc &#234;tre pr&#233;sentes dans de nombreux PDF sans qu'il ne soit pertinent de les supprimer.</p><p>Au lieu de retirer toutes les FontMatrix, nous testerons qu'elles sont au format correct (6 chiffres). Si elles ne le sont pas (si elles contiennent des caract&#232;res non num&#233;riques), elles seront retir&#233;es.</p><p></p><h2>Fin de fichier</h2><p>Le reste d'un fichier PDF contient des objets qui sont utiles pour permettre la lecture du fichier.</p><h4>La table des r&#233;f&#233;rences</h4><p>La <strong>table des r&#233;f&#233;rences</strong> (ou <strong>xref table</strong>) dans un fichier PDF est une structure qui r&#233;pertorie les positions de tous les objets indirects en indiquant leurs d&#233;calages en octets par rapport au d&#233;but du fichier.</p><p>La table des r&#233;f&#233;rences est annonc&#233;e par le mot xref et doit commencer par une ligne contenant deux nombres s&#233;par&#233;s par un espace, repr&#233;sentant le num&#233;ro d'objet du premier objet de cette sous-section et le nombre d'entr&#233;es dans la sous-section.</p><pre><code><code>xref </code>
<code>0 5 </code></code></pre><p>Ici, la table des r&#233;f&#233;rences contient les objets indirects de 0 &#224; 4.</p><p>Chaque ligne de la table des r&#233;f&#233;rences fait 20 octets et contient :</p><pre><code><code>nnnnnnnnnn ggggg m eol</code></code></pre><ul><li><p><code>nnnnnnnnnn</code> : l'offset en octet sur 10 chiffres de l'objet &#224; partir du d&#233;but du fichier</p></li><li><p><code>ggggg</code> : le num&#233;ro de g&#233;n&#233;ration de l'objet sur 5 chiffres (&#224; 65535 pour un objet libre)</p></li><li><p><code>m</code> : un mot cl&#233; sur une lettre qui indique si l'objet est libre (f) ou utilis&#233; (n)</p></li><li><p><code>eol</code> : un marqueur de fin de ligne sur deux caract&#232;res</p></li></ul><p>La table des r&#233;f&#233;rences permet au lecteur PDF de localiser rapidement chaque objet sans devoir analyser l'ensemble du fichier. Plac&#233;e vers la fin du document, cette table est essentielle pour que le lecteur puisse interpr&#233;ter le document correctement et efficacement.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6MiK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6MiK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png 424w, https://substackcdn.com/image/fetch/$s_!6MiK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png 848w, https://substackcdn.com/image/fetch/$s_!6MiK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png 1272w, https://substackcdn.com/image/fetch/$s_!6MiK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6MiK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png" width="1456" height="1219" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1219,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:109827,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6MiK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png 424w, https://substackcdn.com/image/fetch/$s_!6MiK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png 848w, https://substackcdn.com/image/fetch/$s_!6MiK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png 1272w, https://substackcdn.com/image/fetch/$s_!6MiK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78fa0991-df21-4e2b-8290-544503dced29_1584x1326.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>La table commence par un objet nul. Cet objet commence &#224; l'offset 0. Comme il n'est pas utilis&#233; son num&#233;ro de g&#233;n&#233;ration est &#224; 65535 par convention, et le mot cl&#233; est f.</p><p>Les objets suivants sont les diff&#233;rents objets d&#233;finis plus t&#244;t, on ajoute pour chacun leur offset en octet depuis le d&#233;but du fichier, leur num&#233;ro de g&#233;n&#233;ration, puis un n pour annonc&#233; qu'ils sont utilis&#233;s.</p><p><strong>Attention : Bien qu'elle s'appelle table des r&#233;f&#233;rences, c'est techniquement une table de pointeurs.</strong></p><p>Dans un PDF, une <em>r&#233;f&#233;rence</em> et un <em>pointeur </em>jouent des r&#244;les distincts pour organiser et structurer les donn&#233;es.</p><blockquote><p>Une <strong>r&#233;f&#233;rence</strong> est utilis&#233;e pour relier les objets entre eux au sein du fichier. Par exemple, un objet peut contenir une r&#233;f&#233;rence vers un autre objet indirect (identifi&#233; par un num&#233;ro d'objet et de g&#233;n&#233;ration, suivi de "R"), indiquant que son contenu est &#224; rechercher ailleurs dans le document.</p></blockquote><p></p><blockquote><p>Un <strong>pointeur</strong>, quant &#224; lui, indique une position pr&#233;cise dans le fichier PDF. Il est souvent employ&#233; dans la table des r&#233;f&#233;rences (ou <em>xref table</em>) et dans le trailer pour diriger le lecteur PDF vers le d&#233;but de chaque objet indirect. Le pointeur aide le lecteur &#224; retrouver rapidement les &#233;l&#233;ments sans avoir &#224; parcourir tout le fichier.</p></blockquote><p>Ainsi, la r&#233;f&#233;rence est une relation logique entre objets, tandis que le pointeur est une adresse pr&#233;cise dans le fichier.</p><p></p><h4>Le trailer</h4><p>Les PDF doivent &#234;tre lus en partant de la fin, c'est notamment le cas parce que le trailer, derni&#232;re section d'un fichier PDF, est le point d'entr&#233;e pour les lecteurs : il permet de trouver rapidement la table des r&#233;f&#233;rences et l'emplacement de certains objets sp&#233;ciaux.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zN32!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zN32!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png 424w, https://substackcdn.com/image/fetch/$s_!zN32!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png 848w, https://substackcdn.com/image/fetch/$s_!zN32!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png 1272w, https://substackcdn.com/image/fetch/$s_!zN32!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zN32!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png" width="1243" height="1266" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/da65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1266,&quot;width&quot;:1243,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26971,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zN32!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png 424w, https://substackcdn.com/image/fetch/$s_!zN32!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png 848w, https://substackcdn.com/image/fetch/$s_!zN32!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png 1272w, https://substackcdn.com/image/fetch/$s_!zN32!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda65017a-9e5b-46a4-a143-445f44f7b3bf_1243x1266.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Le trailer est un dictionnaire qui contient au moins deux entr&#233;es indispensables :</p><ul><li><p>Une paire cl&#233; valeur /Root qui r&#233;f&#233;rence le catalogue.</p></li><li><p>Une paire cl&#233; valeur /Size qui annonce la taille de la table des r&#233;f&#233;rences.</p></li></ul><p>Enfin, le trailer contient &#233;galement un pointeur vers la table des r&#233;f&#233;rence : le point d'entr&#233;e pour le lecteur qui interpr&#233;tera le fichier.</p><h4>Le marqueur de fin de fichier</h4><p>La derni&#232;re ligne du fichier doit contenir uniquement le marqueur de fin de fichier, %%EOF.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-2hF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-2hF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png 424w, https://substackcdn.com/image/fetch/$s_!-2hF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png 848w, https://substackcdn.com/image/fetch/$s_!-2hF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png 1272w, https://substackcdn.com/image/fetch/$s_!-2hF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-2hF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png" width="1275" height="1386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1386,&quot;width&quot;:1275,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:25164,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-2hF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png 424w, https://substackcdn.com/image/fetch/$s_!-2hF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png 848w, https://substackcdn.com/image/fetch/$s_!-2hF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png 1272w, https://substackcdn.com/image/fetch/$s_!-2hF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48fb3b21-1c2e-457d-b740-a21045be3c4d_1275x1386.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>R&#233;sumons</h3><p>Et voil&#224; ! Nous avons d&#233;sormais construit un PDF infect&#233; au js de toute pi&#232;ce. Mais &#231;a fait beaucoup d'informations. Voici quelques sch&#233;mas pour r&#233;sumer les diff&#233;rents objets et les relations qui les lient.</p><p><strong>Les r&#233;f&#233;rences</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jPJW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jPJW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png 424w, https://substackcdn.com/image/fetch/$s_!jPJW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png 848w, https://substackcdn.com/image/fetch/$s_!jPJW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png 1272w, https://substackcdn.com/image/fetch/$s_!jPJW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jPJW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png" width="1456" height="1132" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1132,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:30596,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jPJW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png 424w, https://substackcdn.com/image/fetch/$s_!jPJW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png 848w, https://substackcdn.com/image/fetch/$s_!jPJW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png 1272w, https://substackcdn.com/image/fetch/$s_!jPJW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F813c0b1f-4d6f-45b8-809e-ad2eefdae274_1551x1206.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Les pointeurs</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9yFF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9yFF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png 424w, https://substackcdn.com/image/fetch/$s_!9yFF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png 848w, https://substackcdn.com/image/fetch/$s_!9yFF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png 1272w, https://substackcdn.com/image/fetch/$s_!9yFF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9yFF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png" width="1456" height="1178" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1178,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:32312,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9yFF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png 424w, https://substackcdn.com/image/fetch/$s_!9yFF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png 848w, https://substackcdn.com/image/fetch/$s_!9yFF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png 1272w, https://substackcdn.com/image/fetch/$s_!9yFF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffaf6436-b606-4291-a1d6-d42ecfb46a59_1530x1238.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>D'autres objets divers</p><p>Si de nombreux types d'objets peuvent &#234;tre le point d'entr&#233;e &#224; l'ex&#233;cution de javascript, la plupart auront besoin d'un objet /JS qui contient le code. Supprimer cet objet s'il est pr&#233;sent permet donc de neutraliser de nombreux chemins d'ex&#233;cution.</p><h1>III - Maintenant, on nettoie tout &#231;a</h1><p>Notre PDF corrompu est pr&#234;t &#224; servir de payload pour tester l'algorithme de nettoyage que nous allons coder. <br>L'algorithme est plut&#244;t simple &#224; r&#233;aliser : on fait la liste des types d'objets qui peuvent contenir du JS susceptible d'&#234;tre ex&#233;cut&#233; et on les "d&#233;sactive".</p><p>Donc, si on r&#233;sume, deux cas de figure ont &#233;t&#233; abord&#233;s dans cet article :</p><ul><li><p>Ceux qui sont normalement d&#233;sactiv&#233;s 99% du temps et qui sont conformes &#224; ce qui est pr&#233;vu par le format : ceux-ci reposent g&#233;n&#233;ralement sur un objet /JS. Supprimer la paire cl&#233;-valeur /JS - code</p></li><li><p>Les FontMatrix, dont on peut v&#233;rifier simplement qu'elles sont au format attendu et les supprimer si ce n'est pas le cas</p></li></ul><p></p><h4>HexaPDF</h4><p>J'ai fait le choix de la gem <a href="https://github.com/gettalong/hexapdf">HexaPDF</a> pour coder mon sanitizer. Celle-ci est tr&#232;s bien cod&#233;e et tr&#232;s utile pour les manipulations de PDF.</p><p>J'utilise pour l'exemple la gem <a href="https://github.com/ruby/stringio">Stringio </a>pour g&#233;rer la sortie du document trait&#233; en m&#233;moire plut&#244;t que d'&#233;crire directement sur le disque. C'est un choix qui m'a permis de simplifier l'int&#233;gration de ce sanitizer &#224; mon application Rails.</p><p><em>Je pr&#233;sente ici un squelette simple pour le sanitizer, comme un script ruby ind&#233;pendant afin de pr&#233;senter la logique. Je vous laisse d&#233;cider des d&#233;tails d'impl&#233;mentation.</em></p><p>On commence par importer les d&#233;pendances :</p><pre><code>require 'hexapdf'
require 'stringio'</code></pre><p>Puis, on cr&#233;e une classe qui servira de sanitizer, avec un peu de setup :</p><pre><code>class Sanitizer
  attr_reader :file_to_sanitize, :result

  def initialize(file_to_sanitize)
    @file_to_sanitize = file_to_sanitize
    @result = { success: false, edited: false }
  end

  def call
    open_pdf
    sanitize_file
    save_pdf
    @result
  end

    def open_pdf
    unless @file_to_sanitize &amp;&amp; File.exist?(@file_to_sanitize) &amp;&amp; File.extname(@file_to_sanitize).downcase == '.pdf'
      print("File #{@file_to_sanitize} not found or not a PDF file\n")
      exit(1)
    end

    @file = HexaPDF::Document.open(@file_to_sanitize)
  end</code></pre><h4>Parcours du fichier</h4><p>Il est ensuite possible de parcourir l'ensemble des objets contenus dans le PDF tr&#232;s simplement :</p><p>On v&#233;rifie si les objets rencontr&#233;s sont des dictionnaires, si c'est le cas, on appelle une fonction de nettoyage sur l'objet. HexaPDF g&#232;re de nombreux aspects de la logique de suppression : cr&#233;ation d'un fichier en m&#233;moire sans les objets supprim&#233;s, ajustement des offsets dans la table des r&#233;f&#233;rences, etc. Ce qui permet de supprimer simplement des objets sans rentre le PDF invalide.</p><pre><code>  def sanitize_file
    @file.each do |obj|
      next unless obj.value.is_a?(Hash)

      sanitize_object(obj)
    end
  end</code></pre><p>La m&#233;thode <code>sanitize_object(obj)</code> d&#233;l&#232;gue ensuite la responsabilit&#233; d'analyser et nettoyer les objets dictionnaires rencontr&#233;s aux m&#233;thodes appropri&#233;es :</p><ul><li><p>Les FontMatrix corrompues (<code>remove_font_matrix</code>)</p></li><li><p>Les actions javascript, notamment l'objet /JS (<code>remove_js</code>)</p></li></ul><p>On &#233;crira une derni&#232;re fonction qui parcourt r&#233;cursivement les &#233;ventuels dictionnaires imbriqu&#233;s pour s'assurer de nettoyer l'ensemble du document.</p><pre><code>  def sanitize_object(obj)
    remove_font_matrix(obj) if obj[:Type] == :Font &amp;&amp; (obj.key?(:FontMatrix) &amp;&amp; corrupt_font_matrix?(obj))

    remove_js(obj) if obj.key?(:JS)

    clean_nested_dictionary(obj) if obj.is_a?(HexaPDF::Dictionary)
  end</code></pre><h4>Les actions javascript</h4><pre><code><code>  def remove_js(obj)</code>
<code>    print("Removing JS from object #{obj.oid}\n")</code>
<code>    obj.delete(:JS)</code>
<code>    @result[:edited] = true</code>
<code>  end</code></code></pre><h4>Les FontMatrix</h4><p>Une premi&#232;re m&#233;thode cherche la fa&#231;on dont la FontMatrix est ins&#233;r&#233;e dans le dictionnaire de police (celle ci peut varier) puis on v&#233;rifie que la matrice ne contient que des valeurs num&#233;riques.</p><pre><code><code>  def corrupt_font_matrix?(obj)</code>
<code>    font_matrix = obj[:FontMatrix].respond_to?(:value) ?              obj[:FontMatrix].value : obj[:FontMatrix]</code>
<code>    font_matrix.any? { |n| !n.is_a?(Numeric) }</code>
<code>  end</code></code></pre><p>Si ce n'est pas le cas, on supprime compl&#232;tement la matrice : puisqu'elle est corrompue, elle n'est pas utile.</p><pre><code><code>  def remove_font_matrix(obj)</code>
<code>    print("Removing corrupt FontMatrix from object #{obj.oid}\n")</code>
<code>    obj.delete(:FontMatrix)</code>
<code>    @result[:edited] = true</code>
<code>  end</code></code></pre><p>Le cas des dictionnaires imbriqu&#233;s</p><p>Afin de nettoyer le code javascript m&#234;me s'il est contenu dans une s&#233;rie de dictionnaires imbriqu&#233;s, on &#233;crit simplement une m&#233;thode r&#233;cursive qui v&#233;rifie et nettoie les entr&#233;es d'un dictionnaires, et le contenu des entr&#233;es qui sont des tableaux.</p><pre><code><code>  def clean_nested_dictionary(dictionary)</code>
<code>    return unless dictionary.is_a?(HexaPDF::Dictionary)</code>

<code>    dictionary.each do |key, value|</code>
<code>      if value.is_a?(HexaPDF::Dictionary)</code>
<code>        sanitize_object(value)</code>
<code>        clean_nested_dictionary(value)</code>
<code>      end</code>

<code>      next unless value.is_a?(Array)</code>

<code>      value.each do |v|</code>
<code>        next unless v.is_a?(HexaPDF::Dictionary)</code>

<code>        sanitize_object(v)</code>
<code>        clean_nested_dictionary(v)</code>
<code>      end</code>
<code>    end</code>
<code>  end</code></code></pre><p>Le nettoyage est termin&#233; !</p><p>On enregistre le fichier PDF s'il a &#233;t&#233; modifi&#233;, et on met &#224; jour le hash de r&#233;sultat du sanitizer.</p><pre><code><code>  def save_pdf</code>
<code>    return unless @result[:edited]</code>

<code>    @io = StringIO.new</code>
<code>    @file.write(@io, optimize: false, validate: false)</code>
<code>    @io.rewind</code>
<code>    @result[:output_data] = @io.string</code>
<code>    @result[:success] = true</code>
<code>  end</code></code></pre><p>Je fais un petit main rapidement pour tester le sanitizer :</p><pre><code><code>if __FILE__ == $0</code>
<code>  if ARGV.empty?</code>
<code>    puts 'Please provide the path to a PDF file to sanitize.'</code>
<code>    exit</code>
<code>  end</code>

<code>  file_to_sanitize = ARGV[0]</code>
<code>  sanitize = Sanitizer.new(file_to_sanitize)</code>
<code>  result = sanitize.call</code>

<code>  if result[:success]</code>
<code>    File.open('sanitized_output.pdf', 'wb') { |f| f.write(result[:output_data]) }</code>
<code>    puts "PDF sanitized successfully and saved as 'sanitized_output.pdf'."</code>
<code>  else</code>
<code>    puts 'PDF sanitization failed.'</code>
<code>  end</code>
<code>end</code></code></pre><p>Je lance &#231;a sur un fichier PDF contenant des actions JS et une FontMatrix corrompue :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G60M!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G60M!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png 424w, https://substackcdn.com/image/fetch/$s_!G60M!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png 848w, https://substackcdn.com/image/fetch/$s_!G60M!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png 1272w, https://substackcdn.com/image/fetch/$s_!G60M!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G60M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png" width="1430" height="632" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:632,&quot;width&quot;:1430,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:139805,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!G60M!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png 424w, https://substackcdn.com/image/fetch/$s_!G60M!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png 848w, https://substackcdn.com/image/fetch/$s_!G60M!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png 1272w, https://substackcdn.com/image/fetch/$s_!G60M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F747c8e2b-85db-44e7-99e7-852ce0ae7551_1430x632.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Et j'obtiens un fichier PDF qui ne contient plus aucun code javascript. Il existe des reliques, comme cet objet indirect Javascript :</p><pre><code><code>9 0 obj</code>
<code>&lt;&lt;</code>
<code>/S/JavaScript</code>
<code>&gt;&gt;</code>
<code>endobj</code>
</code></pre><p>Ce pendant, la section /JS avec le code associ&#233; &#224; l'action JavaScript ont &#233;t&#233; supprim&#233;s, il n'y a donc plus aucun risque d'ex&#233;cution de code.</p><p></p><h3>Quelques remarques pour la fin</h3><p>L'exemple que nous avons vu ici est un cas de PDF extr&#234;mement simple. En r&#233;alit&#233; un simple <em>Hello World</em> g&#233;n&#233;r&#233; par un g&#233;n&#233;rateur de PDF moyen serait bien plus lourd et constitu&#233; de nombreux objets plus complexes.</p><p>Par exemple, nous avons fait appel dans notre document &#224; la police Arial install&#233;e sur votre ordinateur, mais un <em>Hello World</em> en Arial g&#233;n&#233;r&#233; par google docs contiendrait l'int&#233;gralit&#233; de la police afin de s'assurer que le PDF soit visualis&#233; de fa&#231;on exacte par n'importe quel ordinateur, m&#234;me s'il n'a pas cette police install&#233;e.</p><p>Le format PDF permet d&#8217;inclure de nombreuses formes d'actions JavaScript qui n'ont pu &#234;tre pr&#233;sent&#233;es en totalit&#233; dans cet article. Il est important de noter que ces actions sont destin&#233;es &#224; &#234;tre interpr&#233;t&#233;es par des lecteurs PDF sp&#233;cialis&#233;s (comme Adobe Acrobat).</p><p>Les navigateurs web ne permettent g&#233;n&#233;ralement pas cette ex&#233;cution, et l&#8217;interpr&#233;tation<strong> est propre &#224; chaque biblioth&#232;que ou lecteur de PDF.</strong></p><p></p><p>Liens utiles :</p><p><strong>A propos de cet article :</strong></p><ul><li><p><a href="https://gist.github.com/Piwido/7efcfd0f4be36510ede486e6dadbb102">Le fichier minimal d&#233;crit dans cet article (sans js)</a></p></li><li><p><a href="https://gist.github.com/Piwido/bb2c8365360b3f9e0027698e89dfc06d">Un fichier avec diff&#233;rents types d'action JS</a></p></li><li><p><a href="https://codeanlabs.com/wp-content/uploads/2024/05/poc_generalized_CVE-2024-4367.pdf">La POC de la CVE-2024-4367</a></p></li><li><p>Et le <a href="https://gist.github.com/Piwido/b22949b651ad181ded6d9c6306758d04">code du sanitizer pr&#233;sent&#233; dans cet article</a></p></li></ul><p><strong>Le format PDF :</strong></p><ul><li><p><a href="https://speakerdeck.com/ange/lets-write-a-pdf-file">Let's write a pdf file</a> : vulgarisation du format et de la syntaxe</p></li><li><p><a href="https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf">Norme 32000-1:2008 ou PDF 1.7</a> : explications d&#233;taill&#233;es sur le format PDF</p></li></ul><p><strong>Les exploitations de PDF :</strong></p><ul><li><p><a href="https://github.com/luigigubello/PayloadsAllThePDFs">PDF files payload for pentesting</a> : github avec diff&#233;rents fichiers PDF contenant du JS</p></li><li><p><a href="https://portswigger.net/research/portable-data-exfiltration">Portable Data exFiltration: XSS for PDFs</a> : aller plus loin sur les attaques XSS et les PDF</p></li><li><p><a href="https://github.com/cornerpirate/JS2PDFInjector">JS2PDFInjector</a></p></li></ul><p>&#8212; <em>Ines</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪 🚫 On protège nos apps des attaques sans ralentir les tests voilà comment]]></title><description><![CDATA[Temps de lecture : 10 minutes]]></description><link>https://www.rubybiscuit.fr/p/proteger-sans-ralentir-tests-optimises</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/proteger-sans-ralentir-tests-optimises</guid><pubDate>Wed, 02 Oct 2024 14:03:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>&#128680; Cette &#233;dition est relativement longue, votre bo&#238;te mail risque de la tronquer. Je vous conseille de cliquer sur le titre ci-dessus pour l&#8217;ouvrir dans votre navigateur web. &#9757;&#65039;</em></p><p><strong>Au programme aujourd&#8217;hui :</strong></p><ul><li><p><strong>Prot&#233;ger sans ralentir :  tests optimis&#233;s avec Rack Attack </strong><em>par Ines</em></p></li></ul><p><em>Temps de lecture :<strong> 10 minutes</strong></em></p><div><hr></div><p>Hello les petits Biscuits !</p><p>Bienvenue sur la 26&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 530 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w5Nb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" width="1456" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Bonne lecture.</p><div><hr></div><h1><strong>Prot&#233;ger sans ralentir :  tests optimis&#233;s avec Rack Attack</strong></h1><p>En tant que d&#233;veloppeuse junior, lorsque j'ai tent&#233; d'impl&#233;menter Rack Attack et surtout d'acc&#233;l&#233;rer les tests que j'avais r&#233;dig&#233;s, l'exp&#233;rience s'est r&#233;v&#233;l&#233;e &#234;tre un v&#233;ritable casse-t&#234;te. Il y a tr&#232;s peu d'informations l&#224;-dessus sur internet et j'ai eu du mal &#224; comprendre certains aspects de l'utilisation du cache de Rack Attack afin de pouvoir les <strong>mocker</strong>*. J'ai souhait&#233; &#233;crire un article sur le sujet pour vous &#233;pargner ces difficult&#233;s.</p><p><em>* Mock : En programmation orient&#233;e objet, les <strong>mocks </strong>sont des objets simul&#233;s qui reproduisent le comportement d'objets r&#233;els de mani&#232;re contr&#244;l&#233;e.</em></p><p>Rack Attack est une gem en Ruby qui impl&#233;mente un middleware. Elle est particuli&#232;rement utile pour prot&#233;ger les applications Ruby en Rails contre certaines attaques :</p><ul><li><p>par force brute<em> : </em>un attaquant essaye chaque combinaison possible de mot de passe pour un nom d'utilisateur/email, afin d'obtenir l'acc&#232;s au service cibl&#233;</p></li><li><p>par bourrage d'identifiants (credential stuffing) : un attaquant tente une liste d'identifiants compromis r&#233;cup&#233;r&#233;s dans des fuites de donn&#233;es, toujours dans l'objectif d'acc&#233;der au service cibl&#233;</p></li><li><p>par d&#233;ni de service (Denial of Service) : un attaquant submerge votre service de requ&#234;tes jusqu'&#224; ce que le traffic normal ne puisse &#234;tre trait&#233;</p></li></ul><p>Rack Attack se positionne entre votre application et les requ&#234;tes HTTP brutes que celles-ci re&#231;oit, et vous permet d'intercepter celles ci : d&#233;finir des r&#232;gles de limitation de requ&#234;tes, bloquer les IP suspectes ou abusives et m&#234;me mettre en place des listes blanches et noires. Rack Attack utilise moins de ressources par requ&#234;te que votre application Rails, ce qui lui permet de ne pas ralentir les requ&#234;tes tout en les filtrant.</p><p>Aujourd'hui, nous allons voir :</p><ul><li><p>Un exemple simple de configuration Rack Attack</p></li><li><p>Des tests basiques</p></li><li><p>Une explication d&#233;taill&#233;e des interactions de Rack Attack avec le cache</p></li><li><p>Des tests ultra-rapides, ind&#233;pendants du nombre de requ&#234;tes autoris&#233;es</p></li><li><p>Quelques recommendations pour utiliser Rack Attack</p></li></ul><h1>Configuration</h1><p>Pour simplifier cette configuration, nous allons nous concentrer sur les envois de formulaires publics, c'est-&#224;-dire les requ&#234;tes POST o&#249; un formulaire est g&#233;n&#233;ralement envoy&#233; au serveur.</p><p>En effet, c'est en spammant le formulaire de login qu'une attaque par force brute peut &#234;tre men&#233;e, et lors d'une attaque de type Denial of Service (DoS), les requ&#234;tes POST sont bien plus co&#251;teuses pour le serveur que les requ&#234;tes GET, puisqu'il ne s'agit pas seulement de r&#233;pondre &#224; une demande, mais aussi de traiter les donn&#233;es re&#231;ues.</p><p><strong>Installation</strong></p><p>Ajouter la gem au Gemfile :</p><pre><code><code>gem 'rack-attack'</code></code></pre><p>Ensuite, ex&#233;cutez la commande suivante pour installer la gem :</p><pre><code><code>bundle install </code></code></pre><p><strong>Configuration Rack Attack</strong> </p><div class="github-gist" data-attrs="{&quot;innerHTML&quot;:&quot;<div id=\&quot;gist133080068\&quot; class=\&quot;gist\&quot;>\n    <div class=\&quot;gist-file\&quot; translate=\&quot;no\&quot; data-color-mode=\&quot;light\&quot; data-light-theme=\&quot;light\&quot;>\n      <div class=\&quot;gist-data\&quot;>\n        <div class=\&quot;js-gist-file-update-container js-task-list-container\&quot;>\n  <div id=\&quot;file-rack_attack-rb\&quot; class=\&quot;file my-2\&quot;>\n    \n    <div itemprop=\&quot;text\&quot; class=\&quot;Box-body p-0 blob-wrapper data type-ruby  \&quot;>\n\n        \n<div class=\&quot;js-check-bidi js-blob-code-container blob-code-content\&quot;>\n\n  <template class=\&quot;js-file-alert-template\&quot;>\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash flash-warn flash-full d-flex flex-items-center\&quot;>\n  <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n    <span>\n      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n      <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.co/hiddenchars\&quot; target=\&quot;_blank\&quot;>Learn more about bidirectional Unicode characters</a>\n    </span>\n\n\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash-action\&quot;>        <a href=\&quot;{{ revealButtonHref }}\&quot; data-view-component=\&quot;true\&quot; class=\&quot;btn-sm btn\&quot;>    Show hidden characters\n</a>\n</div>\n</div></template>\n<template class=\&quot;js-line-alert-template\&quot;>\n  <span aria-label=\&quot;This line has hidden Unicode characters\&quot; data-view-component=\&quot;true\&quot; class=\&quot;line-alert tooltipped tooltipped-e\&quot;>\n    <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n</span></template>\n\n  <table data-hpc class=\&quot;highlight tab-size js-file-line-container js-code-nav-container js-tagsearch-file\&quot; data-tab-size=\&quot;8\&quot; data-paste-markdown-skip data-tagsearch-lang=\&quot;Ruby\&quot; data-tagsearch-path=\&quot;rack_attack.rb\&quot;>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L1\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;1\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC1\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># config/initializers/rack_attack.rb</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L2\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;2\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC2\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L3\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;3\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC3\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-k>class</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L4\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;4\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC4\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L5\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;5\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC5\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># Configuration for Rack::Attack</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L6\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;6\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC6\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c1>BAN_TIME</span> <span class=pl-c1>=</span> <span class=pl-c1>10</span><span class=pl-kos>.</span><span class=pl-en>minutes</span><span class=pl-kos>.</span><span class=pl-en>freeze</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L7\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;7\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC7\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L8\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;8\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC8\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># GET requests</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L9\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;9\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC9\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c1>MAX_ATTEMPTS_GET</span> <span class=pl-c1>=</span> <span class=pl-c1>20</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L10\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;10\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC10\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c1>OBSERVATION_TIME_GET</span> <span class=pl-c1>=</span> <span class=pl-c1>1</span><span class=pl-kos>.</span><span class=pl-en>minute</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L11\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;11\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC11\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c1>PUBLIC_PATHS_GET</span> <span class=pl-c1>=</span> <span class=pl-kos>[</span><span class=pl-s>&amp;quot;/&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;/sign_in&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;/sign_up&amp;quot;</span><span class=pl-kos>]</span><span class=pl-kos>.</span><span class=pl-en>freeze</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L12\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;12\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC12\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L13\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;13\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC13\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># POST requests</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L14\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;14\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC14\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c1>PUBLIC_PATHS_POST</span> <span class=pl-c1>=</span> <span class=pl-kos>[</span><span class=pl-s>&amp;quot;/sign_in&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;/password&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;/sign_up&amp;quot;</span><span class=pl-kos>]</span><span class=pl-kos>.</span><span class=pl-en>freeze</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L15\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;15\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC15\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c1>MAX_ATTEMPTS_POST</span> <span class=pl-c1>=</span> <span class=pl-c1>25</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L16\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;16\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC16\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c1>OBSERVATION_TIME_POST</span> <span class=pl-c1>=</span> <span class=pl-c1>2</span><span class=pl-kos>.</span><span class=pl-en>minutes</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L17\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;17\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC17\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L18\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;18\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC18\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># Blocks GET requests</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L19\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;19\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC19\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># Blocks for BAN_TIME after MAX_ATTEMPTS_GET requests in OBSERVATION_TIME_GET</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L20\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;20\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC20\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>blocklist</span><span class=pl-kos>(</span><span class=pl-s>&amp;quot;block abusive get requests&amp;quot;</span><span class=pl-kos>)</span> <span class=pl-k>do</span> |<span class=pl-s1>req</span>|</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L21\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;21\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC21\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-v>Allow2Ban</span><span class=pl-kos>.</span><span class=pl-en>filter</span><span class=pl-kos>(</span><span class=pl-s>&amp;quot;public-get:<span class=pl-s1><span class=pl-kos>#{</span><span class=pl-s1>req</span><span class=pl-kos>.</span><span class=pl-en>ip</span><span class=pl-kos>}</span></span>&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>maxretry</span>: <span class=pl-c1>MAX_ATTEMPTS_GET</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L22\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;22\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC22\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                                                           <span class=pl-pds>findtime</span>: <span class=pl-c1>OBSERVATION_TIME_GET</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L23\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;23\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC23\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                                                           <span class=pl-pds>bantime</span>: <span class=pl-c1>BAN_TIME</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L24\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;24\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC24\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-s1>req</span><span class=pl-kos>.</span><span class=pl-en>get?</span> &amp;amp;&amp;amp; <span class=pl-c1>PUBLIC_PATHS_GET</span><span class=pl-kos>.</span><span class=pl-en>include?</span><span class=pl-kos>(</span><span class=pl-s1>req</span><span class=pl-kos>.</span><span class=pl-en>path</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L25\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;25\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC25\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L26\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;26\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC26\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L27\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;27\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC27\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L28\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;28\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC28\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># Blocks POST requests</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L29\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;29\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC29\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># Blocks for BAN_TIME after MAX_ATTEMPTS_POST requests in OBSERVATION_TIME_POST</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L30\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;30\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC30\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>blocklist</span><span class=pl-kos>(</span><span class=pl-s>&amp;quot;block abusive post requests&amp;quot;</span><span class=pl-kos>)</span> <span class=pl-k>do</span> |<span class=pl-s1>req</span>|</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L31\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;31\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC31\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-v>Allow2Ban</span><span class=pl-kos>.</span><span class=pl-en>filter</span><span class=pl-kos>(</span><span class=pl-s>&amp;quot;public-post:<span class=pl-s1><span class=pl-kos>#{</span><span class=pl-s1>req</span><span class=pl-kos>.</span><span class=pl-en>ip</span><span class=pl-kos>}</span></span>&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>maxretry</span>: <span class=pl-c1>MAX_ATTEMPTS_POST</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L32\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;32\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC32\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                                                            <span class=pl-pds>findtime</span>: <span class=pl-c1>OBSERVATION_TIME_POST</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L33\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;33\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC33\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                                                            <span class=pl-pds>bantime</span>: <span class=pl-c1>BAN_TIME</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L34\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;34\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC34\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-s1>req</span><span class=pl-kos>.</span><span class=pl-en>post?</span> &amp;amp;&amp;amp; <span class=pl-c1>PUBLIC_PATHS_POST</span><span class=pl-kos>.</span><span class=pl-en>include?</span><span class=pl-kos>(</span><span class=pl-s1>req</span><span class=pl-kos>.</span><span class=pl-en>path</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L35\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;35\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC35\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L36\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;36\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC36\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L37\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;37\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC37\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L38\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;38\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC38\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># Custom response for blocked requests</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L39\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;39\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC39\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c1>BLOCKED_HTTP_CODE</span> <span class=pl-c1>=</span> <span class=pl-c1>503</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L40\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;40\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC40\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>blocklisted_responder</span> <span class=pl-c1>=</span> <span class=pl-en>lambda</span> <span class=pl-k>do</span> |<span class=pl-s1>request</span>|</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L41\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;41\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC41\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-kos>[</span><span class=pl-c1>BLOCKED_HTTP_CODE</span><span class=pl-kos>,</span> <span class=pl-kos>{</span><span class=pl-kos>}</span><span class=pl-kos>,</span> <span class=pl-kos>[</span><span class=pl-kos>]</span><span class=pl-kos>]</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L42\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;42\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC42\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-rack_attack-rb-L43\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;43\&quot;></td>\n          <td id=\&quot;file-rack_attack-rb-LC43\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-k>end</span></td>\n        </tr>\n  </table>\n</div>\n\n\n    </div>\n\n  </div>\n</div>\n\n      </div>\n      <div class=\&quot;gist-meta\&quot;>\n        <a href=\&quot;https://gist.github.com/Piwido/b46afd1491a8e66dabe49ce09e7c9c63/raw/e941822c9b12c8669154ed9c048a2a0fa4a189d6/rack_attack.rb\&quot; style=\&quot;float:right\&quot; class=\&quot;Link--inTextBlock\&quot;>view raw</a>\n        <a href=\&quot;https://gist.github.com/Piwido/b46afd1491a8e66dabe49ce09e7c9c63#file-rack_attack-rb\&quot; class=\&quot;Link--inTextBlock\&quot;>\n          rack_attack.rb\n        </a>\n        hosted with &amp;#10084; by <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.com\&quot;>GitHub</a>\n      </div>\n    </div>\n</div>\n&quot;,&quot;stylesheet&quot;:&quot;https://github.githubassets.com/assets/gist-embed-26d88def9f88.css&quot;}" data-component-name="GitgistToDOM"><link rel="stylesheet" href="https://github.githubassets.com/assets/gist-embed-26d88def9f88.css"><div id="gist133080068" class="gist">
    <div class="gist-file" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        <div class="js-gist-file-update-container js-task-list-container">
  <div id="file-rack_attack-rb" class="file my-2">
    
    <div itemprop="text" class="Box-body p-0 blob-wrapper data type-ruby  ">

        
<div class="js-check-bidi js-blob-code-container blob-code-content">

  
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  
    

    <span>
      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div>

  <span data-view-component="true" class="line-alert tooltipped tooltipped-e">
    
    

</span>

  <table data-hpc="" class="highlight tab-size js-file-line-container js-code-nav-container js-tagsearch-file" data-tab-size="8" data-paste-markdown-skip="" data-tagsearch-lang="Ruby" data-tagsearch-path="rack_attack.rb">
        <tbody><tr>
          <td id="file-rack_attack-rb-L1" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-rack_attack-rb-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># config/initializers/rack_attack.rb</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L2" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-rack_attack-rb-LC2" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L3" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-rack_attack-rb-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-k">class</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L4" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-rack_attack-rb-LC4" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L5" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-rack_attack-rb-LC5" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># Configuration for Rack::Attack</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L6" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-rack_attack-rb-LC6" class="blob-code blob-code-inner js-file-line">  <span class="pl-c1">BAN_TIME</span> <span class="pl-c1">=</span> <span class="pl-c1">10</span><span class="pl-kos">.</span><span class="pl-en">minutes</span><span class="pl-kos">.</span><span class="pl-en">freeze</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L7" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-rack_attack-rb-LC7" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L8" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-rack_attack-rb-LC8" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># GET requests</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L9" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-rack_attack-rb-LC9" class="blob-code blob-code-inner js-file-line">  <span class="pl-c1">MAX_ATTEMPTS_GET</span> <span class="pl-c1">=</span> <span class="pl-c1">20</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L10" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-rack_attack-rb-LC10" class="blob-code blob-code-inner js-file-line">  <span class="pl-c1">OBSERVATION_TIME_GET</span> <span class="pl-c1">=</span> <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">minute</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L11" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-rack_attack-rb-LC11" class="blob-code blob-code-inner js-file-line">  <span class="pl-c1">PUBLIC_PATHS_GET</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-s">"/"</span><span class="pl-kos">,</span> <span class="pl-s">"/sign_in"</span><span class="pl-kos">,</span> <span class="pl-s">"/sign_up"</span><span class="pl-kos">]</span><span class="pl-kos">.</span><span class="pl-en">freeze</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L12" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-rack_attack-rb-LC12" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L13" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-rack_attack-rb-LC13" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># POST requests</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L14" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-rack_attack-rb-LC14" class="blob-code blob-code-inner js-file-line">  <span class="pl-c1">PUBLIC_PATHS_POST</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-s">"/sign_in"</span><span class="pl-kos">,</span> <span class="pl-s">"/password"</span><span class="pl-kos">,</span> <span class="pl-s">"/sign_up"</span><span class="pl-kos">]</span><span class="pl-kos">.</span><span class="pl-en">freeze</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L15" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-rack_attack-rb-LC15" class="blob-code blob-code-inner js-file-line">  <span class="pl-c1">MAX_ATTEMPTS_POST</span> <span class="pl-c1">=</span> <span class="pl-c1">25</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L16" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-rack_attack-rb-LC16" class="blob-code blob-code-inner js-file-line">  <span class="pl-c1">OBSERVATION_TIME_POST</span> <span class="pl-c1">=</span> <span class="pl-c1">2</span><span class="pl-kos">.</span><span class="pl-en">minutes</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L17" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-rack_attack-rb-LC17" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L18" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-rack_attack-rb-LC18" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># Blocks GET requests</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L19" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-rack_attack-rb-LC19" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># Blocks for BAN_TIME after MAX_ATTEMPTS_GET requests in OBSERVATION_TIME_GET</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L20" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="20"></td>
          <td id="file-rack_attack-rb-LC20" class="blob-code blob-code-inner js-file-line">  <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">blocklist</span><span class="pl-kos">(</span><span class="pl-s">"block abusive get requests"</span><span class="pl-kos">)</span> <span class="pl-k">do</span> |<span class="pl-s1">req</span>|</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L21" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="21"></td>
          <td id="file-rack_attack-rb-LC21" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-v">Allow2Ban</span><span class="pl-kos">.</span><span class="pl-en">filter</span><span class="pl-kos">(</span><span class="pl-s">"public-get:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">req</span><span class="pl-kos">.</span><span class="pl-en">ip</span><span class="pl-kos">}</span></span>"</span><span class="pl-kos">,</span> <span class="pl-pds">maxretry</span>: <span class="pl-c1">MAX_ATTEMPTS_GET</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L22" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="22"></td>
          <td id="file-rack_attack-rb-LC22" class="blob-code blob-code-inner js-file-line">                                                           <span class="pl-pds">findtime</span>: <span class="pl-c1">OBSERVATION_TIME_GET</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L23" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="23"></td>
          <td id="file-rack_attack-rb-LC23" class="blob-code blob-code-inner js-file-line">                                                           <span class="pl-pds">bantime</span>: <span class="pl-c1">BAN_TIME</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L24" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="24"></td>
          <td id="file-rack_attack-rb-LC24" class="blob-code blob-code-inner js-file-line">      <span class="pl-s1">req</span><span class="pl-kos">.</span><span class="pl-en">get?</span> &amp;&amp; <span class="pl-c1">PUBLIC_PATHS_GET</span><span class="pl-kos">.</span><span class="pl-en">include?</span><span class="pl-kos">(</span><span class="pl-s1">req</span><span class="pl-kos">.</span><span class="pl-en">path</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L25" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="25"></td>
          <td id="file-rack_attack-rb-LC25" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L26" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="26"></td>
          <td id="file-rack_attack-rb-LC26" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L27" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="27"></td>
          <td id="file-rack_attack-rb-LC27" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L28" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="28"></td>
          <td id="file-rack_attack-rb-LC28" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># Blocks POST requests</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L29" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="29"></td>
          <td id="file-rack_attack-rb-LC29" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># Blocks for BAN_TIME after MAX_ATTEMPTS_POST requests in OBSERVATION_TIME_POST</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L30" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="30"></td>
          <td id="file-rack_attack-rb-LC30" class="blob-code blob-code-inner js-file-line">  <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">blocklist</span><span class="pl-kos">(</span><span class="pl-s">"block abusive post requests"</span><span class="pl-kos">)</span> <span class="pl-k">do</span> |<span class="pl-s1">req</span>|</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L31" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="31"></td>
          <td id="file-rack_attack-rb-LC31" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-v">Allow2Ban</span><span class="pl-kos">.</span><span class="pl-en">filter</span><span class="pl-kos">(</span><span class="pl-s">"public-post:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">req</span><span class="pl-kos">.</span><span class="pl-en">ip</span><span class="pl-kos">}</span></span>"</span><span class="pl-kos">,</span> <span class="pl-pds">maxretry</span>: <span class="pl-c1">MAX_ATTEMPTS_POST</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L32" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="32"></td>
          <td id="file-rack_attack-rb-LC32" class="blob-code blob-code-inner js-file-line">                                                            <span class="pl-pds">findtime</span>: <span class="pl-c1">OBSERVATION_TIME_POST</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L33" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="33"></td>
          <td id="file-rack_attack-rb-LC33" class="blob-code blob-code-inner js-file-line">                                                            <span class="pl-pds">bantime</span>: <span class="pl-c1">BAN_TIME</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L34" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="34"></td>
          <td id="file-rack_attack-rb-LC34" class="blob-code blob-code-inner js-file-line">      <span class="pl-s1">req</span><span class="pl-kos">.</span><span class="pl-en">post?</span> &amp;&amp; <span class="pl-c1">PUBLIC_PATHS_POST</span><span class="pl-kos">.</span><span class="pl-en">include?</span><span class="pl-kos">(</span><span class="pl-s1">req</span><span class="pl-kos">.</span><span class="pl-en">path</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L35" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="35"></td>
          <td id="file-rack_attack-rb-LC35" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L36" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="36"></td>
          <td id="file-rack_attack-rb-LC36" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L37" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="37"></td>
          <td id="file-rack_attack-rb-LC37" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L38" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="38"></td>
          <td id="file-rack_attack-rb-LC38" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># Custom response for blocked requests</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L39" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="39"></td>
          <td id="file-rack_attack-rb-LC39" class="blob-code blob-code-inner js-file-line">  <span class="pl-c1">BLOCKED_HTTP_CODE</span> <span class="pl-c1">=</span> <span class="pl-c1">503</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L40" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="40"></td>
          <td id="file-rack_attack-rb-LC40" class="blob-code blob-code-inner js-file-line">  <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">blocklisted_responder</span> <span class="pl-c1">=</span> <span class="pl-en">lambda</span> <span class="pl-k">do</span> |<span class="pl-s1">request</span>|</td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L41" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="41"></td>
          <td id="file-rack_attack-rb-LC41" class="blob-code blob-code-inner js-file-line">    <span class="pl-kos">[</span><span class="pl-c1">BLOCKED_HTTP_CODE</span><span class="pl-kos">,</span> <span class="pl-kos">{</span><span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">]</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L42" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="42"></td>
          <td id="file-rack_attack-rb-LC42" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-rack_attack-rb-L43" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="43"></td>
          <td id="file-rack_attack-rb-LC43" class="blob-code blob-code-inner js-file-line"><span class="pl-k">end</span></td>
        </tr>
  </tbody></table>
</div>


    </div>

  </div>
</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/Piwido/b46afd1491a8e66dabe49ce09e7c9c63/raw/e941822c9b12c8669154ed9c048a2a0fa4a189d6/rack_attack.rb" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/Piwido/b46afd1491a8e66dabe49ce09e7c9c63#file-rack_attack-rb" class="Link--inTextBlock">
          rack_attack.rb
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>
</div><p>Si vous n'avez jamais utilis&#233; Rack Attack, pas de panique, on va d&#233;tailler tout &#231;a.</p><p><strong>Cr&#233;er la classe Rack::Attack</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iTUr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iTUr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png 424w, https://substackcdn.com/image/fetch/$s_!iTUr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png 848w, https://substackcdn.com/image/fetch/$s_!iTUr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png 1272w, https://substackcdn.com/image/fetch/$s_!iTUr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iTUr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png" width="638" height="334.33653846153845" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:436,&quot;width&quot;:832,&quot;resizeWidth&quot;:638,&quot;bytes&quot;:55207,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iTUr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png 424w, https://substackcdn.com/image/fetch/$s_!iTUr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png 848w, https://substackcdn.com/image/fetch/$s_!iTUr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png 1272w, https://substackcdn.com/image/fetch/$s_!iTUr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f89daed-5098-4f05-93de-96b65d3096f8_832x436.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Param&#232;tres</strong></p><p>On d&#233;finit tout d'abord les param&#232;tres du filtre dans l'initializer :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Iqss!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Iqss!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png 424w, https://substackcdn.com/image/fetch/$s_!Iqss!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png 848w, https://substackcdn.com/image/fetch/$s_!Iqss!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png 1272w, https://substackcdn.com/image/fetch/$s_!Iqss!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Iqss!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png" width="774" height="652" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:652,&quot;width&quot;:774,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:90430,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Iqss!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png 424w, https://substackcdn.com/image/fetch/$s_!Iqss!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png 848w, https://substackcdn.com/image/fetch/$s_!Iqss!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png 1272w, https://substackcdn.com/image/fetch/$s_!Iqss!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85f7e2eb-78ac-4a04-938c-27f7d11fee6b_774x652.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><em>Note : Dans cet article, j'utilise une limite de tentatives tr&#232;s basse pour simplifier les explications. Je recommande d'utiliser en r&#233;alit&#233; une limite bien moins s&#233;v&#232;re : 50 tentatives en une minute par exemple.</em></p><p><strong>S&#233;lecteurs</strong></p><p>Les s&#233;lecteurs sont les caract&#233;ristiques des requ&#234;tes qui passeront par les filtres. Ils d&#233;terminent quelles seront les requ&#234;tes concern&#233;es par les limitations.</p><p>Puis on choisit le ou les s&#233;lecteurs. Nous s&#233;lectionnons donc la m&#233;thode : <code>POST</code> et le chemin : <code>PUBLIC_PATHS_POST</code>, on obtient le s&#233;lecteur suivant :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-_yI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-_yI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png 424w, https://substackcdn.com/image/fetch/$s_!-_yI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png 848w, https://substackcdn.com/image/fetch/$s_!-_yI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png 1272w, https://substackcdn.com/image/fetch/$s_!-_yI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-_yI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png" width="1004" height="364" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:364,&quot;width&quot;:1004,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48344,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-_yI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png 424w, https://substackcdn.com/image/fetch/$s_!-_yI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png 848w, https://substackcdn.com/image/fetch/$s_!-_yI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png 1272w, https://substackcdn.com/image/fetch/$s_!-_yI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e8fb4b0-f27f-46dd-a53f-77af757a67a1_1004x364.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Dans cet exemple, seules les requ&#234;tes vers l'un des chemins sp&#233;cifi&#233;s et avec pour m&#233;thode post, seront limit&#233;es. Les requ&#234;tes avec pour m&#233;thode get ne seront pas affect&#233;es.</p><p><em>Note : il existe de nombreux autres s&#233;lecteurs possibles (params, url, user-agent, content_length, body...). Vous trouverez <a href="https://medium.com/@cipeinado/unlock-http-info-in-rails-with-rack-request-env-and-uri-c7835397dc14">ici</a> la liste des cl&#233;s utilisables pour request.env et <a href="https://github.com/rack/rack/blob/main/lib/rack/request.rb">ici </a>leur impl&#233;mentation (et traduction) par Rack.</em></p><p><strong>Filtre</strong></p><p>Avec notre nom, nos param&#232;tres et nos s&#233;lecteurs nous pouvons construire le filtre par lequel passeront les requ&#234;tes choisies.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fdWo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fdWo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png 424w, https://substackcdn.com/image/fetch/$s_!fdWo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png 848w, https://substackcdn.com/image/fetch/$s_!fdWo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png 1272w, https://substackcdn.com/image/fetch/$s_!fdWo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fdWo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png" width="1164" height="616" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:616,&quot;width&quot;:1164,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:106322,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fdWo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png 424w, https://substackcdn.com/image/fetch/$s_!fdWo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png 848w, https://substackcdn.com/image/fetch/$s_!fdWo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png 1272w, https://substackcdn.com/image/fetch/$s_!fdWo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fe91708-4ddd-486c-848b-5c2f6710858a_1164x616.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Blocklist</strong></p><p>Lorsque, pour une ip donn&#233;e, l&#8217;utilisateur atteint <code>MAX_ATTEMPTS_POST</code> requ&#234;tes <code>POST</code> durant <code>OBSERVATION_TIME_POST</code> sur l&#8217;ensemble (ou une partie) des <code>PUBLICS_PATH_POST</code>, nous souhaitons qu&#8217;il soit bloqu&#233; de l&#8217;ensemble de l&#8217;application : nous pla&#231;ons donc notre filtre dans une blocklist inconditionnelle.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gEcm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gEcm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png 424w, https://substackcdn.com/image/fetch/$s_!gEcm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png 848w, https://substackcdn.com/image/fetch/$s_!gEcm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png 1272w, https://substackcdn.com/image/fetch/$s_!gEcm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gEcm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png" width="1454" height="760" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:760,&quot;width&quot;:1454,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:156795,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gEcm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png 424w, https://substackcdn.com/image/fetch/$s_!gEcm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png 848w, https://substackcdn.com/image/fetch/$s_!gEcm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png 1272w, https://substackcdn.com/image/fetch/$s_!gEcm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce9a173-a442-49a3-b4f9-3c0f05faa891_1454x760.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><em>Note : Il est possible de n&#8217;appliquer le blocage qu&#8217;&#224; certains types de requ&#234;tes de m&#234;me que pour les filtres.</em></p><p><strong>Un second filtre pour l'exemple</strong></p><p>Pour l&#8217;exemple et afin de mieux comprendre le fonctionnement de Rack Attack, nous allons d&#233;finir un autre filtre sur les requ&#234;tes GET sur des chemins publics, <strong>d&#233;fini avant le filtre sur les requ&#234;tes post</strong> dans notre fichier de configuration (cela aura son importance pour la suite).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t2wv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t2wv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png 424w, https://substackcdn.com/image/fetch/$s_!t2wv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png 848w, https://substackcdn.com/image/fetch/$s_!t2wv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png 1272w, https://substackcdn.com/image/fetch/$s_!t2wv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t2wv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png" width="1426" height="1120" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1120,&quot;width&quot;:1426,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:212386,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t2wv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png 424w, https://substackcdn.com/image/fetch/$s_!t2wv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png 848w, https://substackcdn.com/image/fetch/$s_!t2wv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png 1272w, https://substackcdn.com/image/fetch/$s_!t2wv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F89c72cb4-ade4-4b14-b329-90faf89a83a7_1426x1120.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>R&#233;ponse personnalis&#233;e</strong></p><p>Il est possible de d&#233;finir une r&#233;ponse HTTP personnalis&#233;e pour les requ&#234;tes bloqu&#233;es, la r&#233;ponse par d&#233;faut &#233;tant un 403 (Forbidden) pour les blocklists et 429 (Too many requests) pour les throttles.</p><p>J'ai fait le choix dans cette configuration d'un code 503 sans corps pour une raison tr&#232;s simple. Le code 503 est habituellement utilis&#233; par une plateforme pour signifier qu'elle est hors-service et ne peut traiter les requ&#234;tes entrantes. C'est une fa&#231;on de simuler un crash suite &#224; une &#233;ventuelle attaque par DoS (un peu comme un opossum qui feindrait la mort pour d&#233;courager un pr&#233;dateur peu minutieux).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0nJP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0nJP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png 424w, https://substackcdn.com/image/fetch/$s_!0nJP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png 848w, https://substackcdn.com/image/fetch/$s_!0nJP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png 1272w, https://substackcdn.com/image/fetch/$s_!0nJP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0nJP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png" width="1134" height="544" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/032c1486-1467-41aa-ae28-45c711328288_1134x544.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:544,&quot;width&quot;:1134,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:88934,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0nJP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png 424w, https://substackcdn.com/image/fetch/$s_!0nJP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png 848w, https://substackcdn.com/image/fetch/$s_!0nJP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png 1272w, https://substackcdn.com/image/fetch/$s_!0nJP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F032c1486-1467-41aa-ae28-45c711328288_1134x544.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!A74M!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!A74M!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif 424w, https://substackcdn.com/image/fetch/$s_!A74M!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif 848w, https://substackcdn.com/image/fetch/$s_!A74M!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif 1272w, https://substackcdn.com/image/fetch/$s_!A74M!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!A74M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif" width="498" height="348" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:348,&quot;width&quot;:498,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2381248,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!A74M!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif 424w, https://substackcdn.com/image/fetch/$s_!A74M!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif 848w, https://substackcdn.com/image/fetch/$s_!A74M!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif 1272w, https://substackcdn.com/image/fetch/$s_!A74M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc7b267b-936b-437e-adbe-6ad54ac48d64_498x348.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Votre plateforme face &#224; un bot spammeur</figcaption></figure></div><h1>Tester la configuration</h1><p>Maintenant qu'on a correctement configur&#233; Rack Attack, testons ce que l'on a impl&#233;ment&#233;.</p><p>L'id&#233;e est de v&#233;rifier que les IPs sont bien bloqu&#233;es apr&#232;s un certain nombre de tentatives, puis que le d&#233;blocage se fait correctement une fois le d&#233;lai de bannissement &#233;coul&#233;.</p><p>Nous commencerons par des tests simples et fonctionnels mais lents, puis nous verrons comment les am&#233;liorer.</p><h2>Tests triviaux</h2><p><strong>Premi&#232;re approche des tests de Rack Attack</strong></p><div class="github-gist" data-attrs="{&quot;innerHTML&quot;:&quot;<div id=\&quot;gist133080144\&quot; class=\&quot;gist\&quot;>\n    <div class=\&quot;gist-file\&quot; translate=\&quot;no\&quot; data-color-mode=\&quot;light\&quot; data-light-theme=\&quot;light\&quot;>\n      <div class=\&quot;gist-data\&quot;>\n        <div class=\&quot;js-gist-file-update-container js-task-list-container\&quot;>\n  <div id=\&quot;file-trivial_tests_spec-rb\&quot; class=\&quot;file my-2\&quot;>\n    \n    <div itemprop=\&quot;text\&quot; class=\&quot;Box-body p-0 blob-wrapper data type-ruby  \&quot;>\n\n        \n<div class=\&quot;js-check-bidi js-blob-code-container blob-code-content\&quot;>\n\n  <template class=\&quot;js-file-alert-template\&quot;>\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash flash-warn flash-full d-flex flex-items-center\&quot;>\n  <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n    <span>\n      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n      <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.co/hiddenchars\&quot; target=\&quot;_blank\&quot;>Learn more about bidirectional Unicode characters</a>\n    </span>\n\n\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash-action\&quot;>        <a href=\&quot;{{ revealButtonHref }}\&quot; data-view-component=\&quot;true\&quot; class=\&quot;btn-sm btn\&quot;>    Show hidden characters\n</a>\n</div>\n</div></template>\n<template class=\&quot;js-line-alert-template\&quot;>\n  <span aria-label=\&quot;This line has hidden Unicode characters\&quot; data-view-component=\&quot;true\&quot; class=\&quot;line-alert tooltipped tooltipped-e\&quot;>\n    <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n</span></template>\n\n  <table data-hpc class=\&quot;highlight tab-size js-file-line-container js-code-nav-container js-tagsearch-file\&quot; data-tab-size=\&quot;8\&quot; data-paste-markdown-skip data-tagsearch-lang=\&quot;Ruby\&quot; data-tagsearch-path=\&quot;trivial_tests_spec.rb\&quot;>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L1\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;1\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC1\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-en>require</span> <span class=pl-s>&amp;quot;rails_helper&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L2\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;2\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC2\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L3\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;3\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC3\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-v>RSpec</span><span class=pl-kos>.</span><span class=pl-en>describe</span> <span class=pl-s>&amp;quot;Rack::Attack&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>type</span>: <span class=pl-pds>:request</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L4\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;4\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC4\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L5\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;5\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC5\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>before</span><span class=pl-kos>(</span><span class=pl-pds>:all</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L6\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;6\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC6\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>enabled</span> <span class=pl-c1>=</span> <span class=pl-c1>true</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L7\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;7\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC7\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>cache</span><span class=pl-kos>.</span><span class=pl-en>store</span> <span class=pl-c1>=</span> <span class=pl-v>ActiveSupport</span>::<span class=pl-v>Cache</span>::<span class=pl-v>MemoryStore</span><span class=pl-kos>.</span><span class=pl-en>new</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L8\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;8\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC8\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L9\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;9\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC9\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L10\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;10\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC10\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>after</span><span class=pl-kos>(</span><span class=pl-pds>:all</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L11\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;11\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC11\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>enabled</span> <span class=pl-c1>=</span> <span class=pl-c1>false</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L12\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;12\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC12\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L13\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;13\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC13\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L14\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;14\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC14\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>before</span><span class=pl-kos>(</span><span class=pl-pds>:each</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L15\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;15\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC15\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>cache</span><span class=pl-kos>.</span><span class=pl-en>store</span><span class=pl-kos>.</span><span class=pl-en>clear</span> <span class=pl-c># Avoid tests overlaps</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L16\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;16\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC16\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L17\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;17\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC17\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L18\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;18\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC18\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:ban_time</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>BAN_TIME</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L19\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;19\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC19\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:store</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>cache</span><span class=pl-kos>.</span><span class=pl-en>store</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L20\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;20\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC20\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:filters_list_regex</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-sr>/public-(post|get)/i</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L21\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;21\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC21\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:ip</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;1.2.3.4&amp;quot;</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L22\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;22\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC22\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L23\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;23\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC23\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L24\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;24\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC24\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># Tester que Rack Attack bloque seulement apr&#232;s MAX_ATTEMPTS tentatives</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L25\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;25\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC25\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-en>shared_examples</span> <span class=pl-s>&amp;quot;rate limiting and IP blocking&amp;quot;</span> <span class=pl-k>do</span> |<span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span> <span class=pl-s1>max_attempts</span><span class=pl-kos>,</span> <span class=pl-s1>filter</span>|</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L26\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;26\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC26\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>it</span> <span class=pl-s>&amp;quot;only blocks IP after MAX_ATTEMPTS <span class=pl-s1><span class=pl-kos>#{</span><span class=pl-s1>http_method</span><span class=pl-kos>}</span></span> requests&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>aggregate_failures</span>: <span class=pl-c1>true</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L27\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;27\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC27\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-s1>max_attempts</span><span class=pl-kos>.</span><span class=pl-en>times</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L28\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;28\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC28\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-en>public_send</span><span class=pl-kos>(</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L29\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;29\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC29\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-s1>http_method</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L30\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;30\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC30\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-s1>path</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L31\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;31\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC31\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-pds>params</span>: <span class=pl-c1>nil</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L32\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;32\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC32\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-pds>headers</span>: <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;REMOTE_ADDR&amp;quot;</span> <span class=pl-c1>=&amp;gt;</span> <span class=pl-en>ip</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L33\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;33\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC33\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L34\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;34\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC34\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>response</span><span class=pl-kos>.</span><span class=pl-en>status</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>not_to</span> <span class=pl-en>eq</span><span class=pl-kos>(</span><span class=pl-c1>503</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L35\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;35\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC35\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L36\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;36\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC36\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L37\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;37\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC37\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>public_send</span><span class=pl-kos>(</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L38\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;38\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC38\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>http_method</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L39\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;39\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC39\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-s1>path</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L40\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;40\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC40\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-pds>params</span>: <span class=pl-c1>nil</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L41\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;41\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC41\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-pds>headers</span>: <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;REMOTE_ADDR&amp;quot;</span> <span class=pl-c1>=&amp;gt;</span> <span class=pl-en>ip</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L42\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;42\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC42\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L43\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;43\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC43\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>response</span><span class=pl-kos>.</span><span class=pl-en>status</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>to</span> <span class=pl-en>eq</span><span class=pl-kos>(</span><span class=pl-c1>503</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L44\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;44\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC44\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L45\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;45\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC45\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L46\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;46\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC46\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L47\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;47\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC47\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># Tester que Rack Attack d&#233;bloque apr&#232;s BAN TIME</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L48\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;48\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC48\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>shared_examples</span> <span class=pl-s>&amp;quot;IP unblocking&amp;quot;</span> <span class=pl-k>do</span> |<span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span> <span class=pl-s1>max_attempts</span><span class=pl-kos>,</span>  <span class=pl-s1>filter</span>|</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L49\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;49\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC49\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>before</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L50\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;50\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC50\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-kos>(</span><span class=pl-s1>max_attempts</span> + <span class=pl-c1>1</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>times</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L51\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;51\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC51\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-en>public_send</span><span class=pl-kos>(</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L52\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;52\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC52\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-s1>http_method</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L53\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;53\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC53\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-s1>path</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L54\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;54\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC54\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-pds>params</span>: <span class=pl-c1>nil</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L55\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;55\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC55\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-pds>headers</span>: <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;REMOTE_ADDR&amp;quot;</span> <span class=pl-c1>=&amp;gt;</span> <span class=pl-en>ip</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L56\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;56\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC56\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L57\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;57\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC57\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L58\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;58\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC58\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L59\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;59\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC59\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L60\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;60\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC60\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>it</span> <span class=pl-s>&amp;quot;unblock IP after BAN_TIME for <span class=pl-s1><span class=pl-kos>#{</span><span class=pl-s1>http_method</span><span class=pl-kos>}</span></span> requests&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L61\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;61\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC61\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>travel_to</span><span class=pl-kos>(</span><span class=pl-v>Time</span><span class=pl-kos>.</span><span class=pl-en>current</span> + <span class=pl-en>ban_time</span> + <span class=pl-c1>1</span><span class=pl-kos>.</span><span class=pl-en>minute</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L62\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;62\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC62\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-en>public_send</span><span class=pl-kos>(</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L63\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;63\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC63\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L64\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;64\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC64\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-pds>params</span>: <span class=pl-c1>nil</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L65\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;65\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC65\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>          <span class=pl-pds>headers</span>: <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;REMOTE_ADDR&amp;quot;</span> <span class=pl-c1>=&amp;gt;</span> <span class=pl-en>ip</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L66\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;66\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC66\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L67\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;67\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC67\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>response</span><span class=pl-kos>.</span><span class=pl-en>status</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>not_to</span> <span class=pl-en>eq</span><span class=pl-kos>(</span><span class=pl-c1>503</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L68\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;68\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC68\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L69\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;69\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC69\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L70\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;70\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC70\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L71\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;71\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC71\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L72\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;72\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC72\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>describe</span> <span class=pl-s>&amp;quot;POST requests&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L73\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;73\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC73\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>context</span> <span class=pl-s>&amp;quot;when the ip is not banned&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L74\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;74\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC74\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>it_behaves_like</span> <span class=pl-s>&amp;quot;rate limiting and IP blocking&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>:post</span><span class=pl-kos>,</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>PUBLIC_PATHS_POST</span><span class=pl-kos>.</span><span class=pl-en>first</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L75\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;75\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC75\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>MAX_ATTEMPTS_POST</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;public-post&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L76\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;76\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC76\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L77\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;77\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC77\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L78\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;78\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC78\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>context</span> <span class=pl-s>&amp;quot;when the ip is banned&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L79\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;79\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC79\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>it_behaves_like</span> <span class=pl-s>&amp;quot;IP unblocking&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;post&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>PUBLIC_PATHS_POST</span><span class=pl-kos>.</span><span class=pl-en>first</span><span class=pl-kos>,</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>MAX_ATTEMPTS_POST</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;public-post&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L80\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;80\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC80\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L81\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;81\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC81\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L82\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;82\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC82\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L83\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;83\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC83\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>describe</span> <span class=pl-s>&amp;quot;GET requests&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L84\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;84\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC84\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>context</span> <span class=pl-s>&amp;quot;when the ip is not banned&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L85\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;85\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC85\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>it_behaves_like</span> <span class=pl-s>&amp;quot;rate limiting and IP blocking&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>:get</span><span class=pl-kos>,</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>PUBLIC_PATHS_GET</span><span class=pl-kos>.</span><span class=pl-en>first</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L86\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;86\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC86\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>MAX_ATTEMPTS_GET</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;public-get&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L87\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;87\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC87\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L88\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;88\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC88\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L89\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;89\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC89\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>context</span> <span class=pl-s>&amp;quot;when the ip is banned&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L90\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;90\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC90\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>it_behaves_like</span> <span class=pl-s>&amp;quot;IP unblocking&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;get&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>PUBLIC_PATHS_GET</span><span class=pl-kos>.</span><span class=pl-en>first</span><span class=pl-kos>,</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>MAX_ATTEMPTS_GET</span><span class=pl-kos>,</span> <span class=pl-s>&amp;quot;public-get&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L91\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;91\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC91\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L92\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;92\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC92\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-trivial_tests_spec-rb-L93\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;93\&quot;></td>\n          <td id=\&quot;file-trivial_tests_spec-rb-LC93\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-k>end</span></td>\n        </tr>\n  </table>\n</div>\n\n\n    </div>\n\n  </div>\n</div>\n\n      </div>\n      <div class=\&quot;gist-meta\&quot;>\n        <a href=\&quot;https://gist.github.com/Piwido/c3cbf3ea77511fb74e0b540b2ad0faee/raw/338299e4b989b2f77c9c3cb0d7df384805bd768f/trivial_tests_spec.rb\&quot; style=\&quot;float:right\&quot; class=\&quot;Link--inTextBlock\&quot;>view raw</a>\n        <a href=\&quot;https://gist.github.com/Piwido/c3cbf3ea77511fb74e0b540b2ad0faee#file-trivial_tests_spec-rb\&quot; class=\&quot;Link--inTextBlock\&quot;>\n          trivial_tests_spec.rb\n        </a>\n        hosted with &amp;#10084; by <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.com\&quot;>GitHub</a>\n      </div>\n    </div>\n</div>\n&quot;,&quot;stylesheet&quot;:&quot;https://github.githubassets.com/assets/gist-embed-26d88def9f88.css&quot;}" data-component-name="GitgistToDOM"><link rel="stylesheet" href="https://github.githubassets.com/assets/gist-embed-26d88def9f88.css"><div id="gist133080144" class="gist">
    <div class="gist-file" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        <div class="js-gist-file-update-container js-task-list-container">
  <div id="file-trivial_tests_spec-rb" class="file my-2">
    
    <div itemprop="text" class="Box-body p-0 blob-wrapper data type-ruby  ">

        
<div class="js-check-bidi js-blob-code-container blob-code-content">

  
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  
    

    <span>
      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div>

  <span data-view-component="true" class="line-alert tooltipped tooltipped-e">
    
    

</span>

  <table data-hpc="" class="highlight tab-size js-file-line-container js-code-nav-container js-tagsearch-file" data-tab-size="8" data-paste-markdown-skip="" data-tagsearch-lang="Ruby" data-tagsearch-path="trivial_tests_spec.rb">
        <tbody><tr>
          <td id="file-trivial_tests_spec-rb-L1" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-trivial_tests_spec-rb-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-en">require</span> <span class="pl-s">"rails_helper"</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L2" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-trivial_tests_spec-rb-LC2" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L3" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-trivial_tests_spec-rb-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-v">RSpec</span><span class="pl-kos">.</span><span class="pl-en">describe</span> <span class="pl-s">"Rack::Attack"</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-pds">:request</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L4" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-trivial_tests_spec-rb-LC4" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L5" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-trivial_tests_spec-rb-LC5" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">before</span><span class="pl-kos">(</span><span class="pl-pds">:all</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L6" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-trivial_tests_spec-rb-LC6" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">enabled</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L7" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-trivial_tests_spec-rb-LC7" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">store</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">Cache</span>::<span class="pl-v">MemoryStore</span><span class="pl-kos">.</span><span class="pl-en">new</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L8" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-trivial_tests_spec-rb-LC8" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L9" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-trivial_tests_spec-rb-LC9" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L10" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-trivial_tests_spec-rb-LC10" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">after</span><span class="pl-kos">(</span><span class="pl-pds">:all</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L11" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-trivial_tests_spec-rb-LC11" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">enabled</span> <span class="pl-c1">=</span> <span class="pl-c1">false</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L12" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-trivial_tests_spec-rb-LC12" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L13" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-trivial_tests_spec-rb-LC13" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L14" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-trivial_tests_spec-rb-LC14" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">before</span><span class="pl-kos">(</span><span class="pl-pds">:each</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L15" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-trivial_tests_spec-rb-LC15" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">store</span><span class="pl-kos">.</span><span class="pl-en">clear</span> <span class="pl-c"># Avoid tests overlaps</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L16" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-trivial_tests_spec-rb-LC16" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L17" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-trivial_tests_spec-rb-LC17" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L18" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-trivial_tests_spec-rb-LC18" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:ban_time</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">BAN_TIME</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L19" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-trivial_tests_spec-rb-LC19" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:store</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">store</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L20" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="20"></td>
          <td id="file-trivial_tests_spec-rb-LC20" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:filters_list_regex</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-sr">/public-(post|get)/i</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L21" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="21"></td>
          <td id="file-trivial_tests_spec-rb-LC21" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:ip</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-s">"1.2.3.4"</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L22" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="22"></td>
          <td id="file-trivial_tests_spec-rb-LC22" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L23" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="23"></td>
          <td id="file-trivial_tests_spec-rb-LC23" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L24" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="24"></td>
          <td id="file-trivial_tests_spec-rb-LC24" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># Tester que Rack Attack bloque seulement apr&#232;s MAX_ATTEMPTS tentatives</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L25" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="25"></td>
          <td id="file-trivial_tests_spec-rb-LC25" class="blob-code blob-code-inner js-file-line"><span class="pl-en">shared_examples</span> <span class="pl-s">"rate limiting and IP blocking"</span> <span class="pl-k">do</span> |<span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-s1">max_attempts</span><span class="pl-kos">,</span> <span class="pl-s1">filter</span>|</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L26" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="26"></td>
          <td id="file-trivial_tests_spec-rb-LC26" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">it</span> <span class="pl-s">"only blocks IP after MAX_ATTEMPTS <span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">http_method</span><span class="pl-kos">}</span></span> requests"</span><span class="pl-kos">,</span> <span class="pl-pds">aggregate_failures</span>: <span class="pl-c1">true</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L27" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="27"></td>
          <td id="file-trivial_tests_spec-rb-LC27" class="blob-code blob-code-inner js-file-line">      <span class="pl-s1">max_attempts</span><span class="pl-kos">.</span><span class="pl-en">times</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L28" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="28"></td>
          <td id="file-trivial_tests_spec-rb-LC28" class="blob-code blob-code-inner js-file-line">        <span class="pl-en">public_send</span><span class="pl-kos">(</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L29" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="29"></td>
          <td id="file-trivial_tests_spec-rb-LC29" class="blob-code blob-code-inner js-file-line">          <span class="pl-s1">http_method</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L30" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="30"></td>
          <td id="file-trivial_tests_spec-rb-LC30" class="blob-code blob-code-inner js-file-line">          <span class="pl-s1">path</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L31" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="31"></td>
          <td id="file-trivial_tests_spec-rb-LC31" class="blob-code blob-code-inner js-file-line">          <span class="pl-pds">params</span>: <span class="pl-c1">nil</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L32" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="32"></td>
          <td id="file-trivial_tests_spec-rb-LC32" class="blob-code blob-code-inner js-file-line">          <span class="pl-pds">headers</span>: <span class="pl-kos">{</span> <span class="pl-s">"REMOTE_ADDR"</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">ip</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L33" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="33"></td>
          <td id="file-trivial_tests_spec-rb-LC33" class="blob-code blob-code-inner js-file-line">        <span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L34" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="34"></td>
          <td id="file-trivial_tests_spec-rb-LC34" class="blob-code blob-code-inner js-file-line">        <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">status</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">not_to</span> <span class="pl-en">eq</span><span class="pl-kos">(</span><span class="pl-c1">503</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L35" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="35"></td>
          <td id="file-trivial_tests_spec-rb-LC35" class="blob-code blob-code-inner js-file-line">      <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L36" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="36"></td>
          <td id="file-trivial_tests_spec-rb-LC36" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L37" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="37"></td>
          <td id="file-trivial_tests_spec-rb-LC37" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">public_send</span><span class="pl-kos">(</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L38" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="38"></td>
          <td id="file-trivial_tests_spec-rb-LC38" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">http_method</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L39" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="39"></td>
          <td id="file-trivial_tests_spec-rb-LC39" class="blob-code blob-code-inner js-file-line">        <span class="pl-s1">path</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L40" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="40"></td>
          <td id="file-trivial_tests_spec-rb-LC40" class="blob-code blob-code-inner js-file-line">        <span class="pl-pds">params</span>: <span class="pl-c1">nil</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L41" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="41"></td>
          <td id="file-trivial_tests_spec-rb-LC41" class="blob-code blob-code-inner js-file-line">        <span class="pl-pds">headers</span>: <span class="pl-kos">{</span> <span class="pl-s">"REMOTE_ADDR"</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">ip</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L42" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="42"></td>
          <td id="file-trivial_tests_spec-rb-LC42" class="blob-code blob-code-inner js-file-line">      <span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L43" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="43"></td>
          <td id="file-trivial_tests_spec-rb-LC43" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">status</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to</span> <span class="pl-en">eq</span><span class="pl-kos">(</span><span class="pl-c1">503</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L44" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="44"></td>
          <td id="file-trivial_tests_spec-rb-LC44" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L45" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="45"></td>
          <td id="file-trivial_tests_spec-rb-LC45" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L46" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="46"></td>
          <td id="file-trivial_tests_spec-rb-LC46" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L47" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="47"></td>
          <td id="file-trivial_tests_spec-rb-LC47" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># Tester que Rack Attack d&#233;bloque apr&#232;s BAN TIME</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L48" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="48"></td>
          <td id="file-trivial_tests_spec-rb-LC48" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">shared_examples</span> <span class="pl-s">"IP unblocking"</span> <span class="pl-k">do</span> |<span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-s1">max_attempts</span><span class="pl-kos">,</span>  <span class="pl-s1">filter</span>|</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L49" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="49"></td>
          <td id="file-trivial_tests_spec-rb-LC49" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">before</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L50" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="50"></td>
          <td id="file-trivial_tests_spec-rb-LC50" class="blob-code blob-code-inner js-file-line">      <span class="pl-kos">(</span><span class="pl-s1">max_attempts</span> + <span class="pl-c1">1</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">times</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L51" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="51"></td>
          <td id="file-trivial_tests_spec-rb-LC51" class="blob-code blob-code-inner js-file-line">        <span class="pl-en">public_send</span><span class="pl-kos">(</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L52" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="52"></td>
          <td id="file-trivial_tests_spec-rb-LC52" class="blob-code blob-code-inner js-file-line">          <span class="pl-s1">http_method</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L53" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="53"></td>
          <td id="file-trivial_tests_spec-rb-LC53" class="blob-code blob-code-inner js-file-line">          <span class="pl-s1">path</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L54" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="54"></td>
          <td id="file-trivial_tests_spec-rb-LC54" class="blob-code blob-code-inner js-file-line">          <span class="pl-pds">params</span>: <span class="pl-c1">nil</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L55" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="55"></td>
          <td id="file-trivial_tests_spec-rb-LC55" class="blob-code blob-code-inner js-file-line">          <span class="pl-pds">headers</span>: <span class="pl-kos">{</span> <span class="pl-s">"REMOTE_ADDR"</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">ip</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L56" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="56"></td>
          <td id="file-trivial_tests_spec-rb-LC56" class="blob-code blob-code-inner js-file-line">        <span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L57" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="57"></td>
          <td id="file-trivial_tests_spec-rb-LC57" class="blob-code blob-code-inner js-file-line">      <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L58" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="58"></td>
          <td id="file-trivial_tests_spec-rb-LC58" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L59" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="59"></td>
          <td id="file-trivial_tests_spec-rb-LC59" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L60" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="60"></td>
          <td id="file-trivial_tests_spec-rb-LC60" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">it</span> <span class="pl-s">"unblock IP after BAN_TIME for <span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">http_method</span><span class="pl-kos">}</span></span> requests"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L61" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="61"></td>
          <td id="file-trivial_tests_spec-rb-LC61" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">travel_to</span><span class="pl-kos">(</span><span class="pl-v">Time</span><span class="pl-kos">.</span><span class="pl-en">current</span> + <span class="pl-en">ban_time</span> + <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">minute</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L62" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="62"></td>
          <td id="file-trivial_tests_spec-rb-LC62" class="blob-code blob-code-inner js-file-line">        <span class="pl-en">public_send</span><span class="pl-kos">(</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L63" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="63"></td>
          <td id="file-trivial_tests_spec-rb-LC63" class="blob-code blob-code-inner js-file-line">          <span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L64" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="64"></td>
          <td id="file-trivial_tests_spec-rb-LC64" class="blob-code blob-code-inner js-file-line">          <span class="pl-pds">params</span>: <span class="pl-c1">nil</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L65" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="65"></td>
          <td id="file-trivial_tests_spec-rb-LC65" class="blob-code blob-code-inner js-file-line">          <span class="pl-pds">headers</span>: <span class="pl-kos">{</span> <span class="pl-s">"REMOTE_ADDR"</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">ip</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L66" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="66"></td>
          <td id="file-trivial_tests_spec-rb-LC66" class="blob-code blob-code-inner js-file-line">        <span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L67" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="67"></td>
          <td id="file-trivial_tests_spec-rb-LC67" class="blob-code blob-code-inner js-file-line">        <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">status</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">not_to</span> <span class="pl-en">eq</span><span class="pl-kos">(</span><span class="pl-c1">503</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L68" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="68"></td>
          <td id="file-trivial_tests_spec-rb-LC68" class="blob-code blob-code-inner js-file-line">      <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L69" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="69"></td>
          <td id="file-trivial_tests_spec-rb-LC69" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L70" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="70"></td>
          <td id="file-trivial_tests_spec-rb-LC70" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L71" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="71"></td>
          <td id="file-trivial_tests_spec-rb-LC71" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L72" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="72"></td>
          <td id="file-trivial_tests_spec-rb-LC72" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">describe</span> <span class="pl-s">"POST requests"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L73" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="73"></td>
          <td id="file-trivial_tests_spec-rb-LC73" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">context</span> <span class="pl-s">"when the ip is not banned"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L74" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="74"></td>
          <td id="file-trivial_tests_spec-rb-LC74" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">it_behaves_like</span> <span class="pl-s">"rate limiting and IP blocking"</span><span class="pl-kos">,</span> <span class="pl-pds">:post</span><span class="pl-kos">,</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">PUBLIC_PATHS_POST</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L75" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="75"></td>
          <td id="file-trivial_tests_spec-rb-LC75" class="blob-code blob-code-inner js-file-line">                      <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">MAX_ATTEMPTS_POST</span><span class="pl-kos">,</span> <span class="pl-s">"public-post"</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L76" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="76"></td>
          <td id="file-trivial_tests_spec-rb-LC76" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L77" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="77"></td>
          <td id="file-trivial_tests_spec-rb-LC77" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L78" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="78"></td>
          <td id="file-trivial_tests_spec-rb-LC78" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">context</span> <span class="pl-s">"when the ip is banned"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L79" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="79"></td>
          <td id="file-trivial_tests_spec-rb-LC79" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">it_behaves_like</span> <span class="pl-s">"IP unblocking"</span><span class="pl-kos">,</span> <span class="pl-s">"post"</span><span class="pl-kos">,</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">PUBLIC_PATHS_POST</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">,</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">MAX_ATTEMPTS_POST</span><span class="pl-kos">,</span> <span class="pl-s">"public-post"</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L80" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="80"></td>
          <td id="file-trivial_tests_spec-rb-LC80" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L81" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="81"></td>
          <td id="file-trivial_tests_spec-rb-LC81" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L82" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="82"></td>
          <td id="file-trivial_tests_spec-rb-LC82" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L83" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="83"></td>
          <td id="file-trivial_tests_spec-rb-LC83" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">describe</span> <span class="pl-s">"GET requests"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L84" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="84"></td>
          <td id="file-trivial_tests_spec-rb-LC84" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">context</span> <span class="pl-s">"when the ip is not banned"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L85" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="85"></td>
          <td id="file-trivial_tests_spec-rb-LC85" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">it_behaves_like</span> <span class="pl-s">"rate limiting and IP blocking"</span><span class="pl-kos">,</span> <span class="pl-pds">:get</span><span class="pl-kos">,</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">PUBLIC_PATHS_GET</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L86" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="86"></td>
          <td id="file-trivial_tests_spec-rb-LC86" class="blob-code blob-code-inner js-file-line">                      <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">MAX_ATTEMPTS_GET</span><span class="pl-kos">,</span> <span class="pl-s">"public-get"</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L87" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="87"></td>
          <td id="file-trivial_tests_spec-rb-LC87" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L88" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="88"></td>
          <td id="file-trivial_tests_spec-rb-LC88" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L89" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="89"></td>
          <td id="file-trivial_tests_spec-rb-LC89" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">context</span> <span class="pl-s">"when the ip is banned"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L90" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="90"></td>
          <td id="file-trivial_tests_spec-rb-LC90" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">it_behaves_like</span> <span class="pl-s">"IP unblocking"</span><span class="pl-kos">,</span> <span class="pl-s">"get"</span><span class="pl-kos">,</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">PUBLIC_PATHS_GET</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">,</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">MAX_ATTEMPTS_GET</span><span class="pl-kos">,</span> <span class="pl-s">"public-get"</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L91" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="91"></td>
          <td id="file-trivial_tests_spec-rb-LC91" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L92" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="92"></td>
          <td id="file-trivial_tests_spec-rb-LC92" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-trivial_tests_spec-rb-L93" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="93"></td>
          <td id="file-trivial_tests_spec-rb-LC93" class="blob-code blob-code-inner js-file-line"><span class="pl-k">end</span></td>
        </tr>
  </tbody></table>
</div>


    </div>

  </div>
</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/Piwido/c3cbf3ea77511fb74e0b540b2ad0faee/raw/338299e4b989b2f77c9c3cb0d7df384805bd768f/trivial_tests_spec.rb" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/Piwido/c3cbf3ea77511fb74e0b540b2ad0faee#file-trivial_tests_spec-rb" class="Link--inTextBlock">
          trivial_tests_spec.rb
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>
</div><p><strong>Limites des tests triviaux</strong></p><p>De tels tests fonctionnent, et permettent effectivement de v&#233;rifier que la configuration Rack Attack a &#233;t&#233; correctement r&#233;alis&#233;e. Cependant, leur dur&#233;e d&#233;pend du nombre de requ&#234;tes autoris&#233;es par chaque filtre, puisqu&#8217;il faut en simuler autant pour observer le blocage.</p><p>Ces tests peuvent donc s&#8217;av&#233;rer tr&#232;s longs lorsque l&#8217;on souhaite utiliser des p&#233;riodes d&#8217;observation plus longues avec des nombres de tentatives maximales pour un filtre &#233;lev&#233;es ou lorsqu&#8217;il y a plusieurs filtres &#224; tester (jusqu&#8217;&#224; <strong>15 secondes</strong> pour un seul filtre test&#233; avec 100 requ&#234;tes GET autoris&#233;es sur un ordinateur relativement puissant).</p><p>De la m&#234;me fa&#231;on, le succ&#232;s du test d&#233;pend dans ce contexte de la vitesse d'ex&#233;cution de l'environnement o&#249; le test est lanc&#233;. Si les requ&#234;tes mettent trop de temps &#224; s'ex&#233;cuter on peut ne jamais atteindre la limite durant le temps d'observation. Et si l'on souhaite &#233;galement autoriser beaucoup de requ&#234;tes sur une tr&#232;s courte p&#233;riode, le blocage est pratiquement impossible &#224; tester : il est tr&#232;s difficile d'envoyer un nombre &#233;lev&#233; de requ&#234;tes en une p&#233;riode tr&#232;s courte avec les helper fournis par RSpec (ou rack-test).</p><p>Nous allons donc voir comment les appels au cache sont faits afin de mocker le nombre de requ&#234;tes qui nous int&#233;resse (voire directement un blocage) et rendre ainsi le temps de test ind&#233;pendant du nombre de requ&#234;tes autoris&#233;es pour chaque filtre.</p><h2>Comprendre les appels au cache</h2><p><strong>RackAttack</strong> utilise le cache pour stocker des informations sur les requ&#234;tes effectu&#233;es par les utilisateurs ou les adresses IP afin d'optimiser les performances et d'appliquer des r&#232;gles de limitation en co&#251;tant peu de ressources.</p><p>Afin d'illustrer les appels au cache effectu&#233;s par Rack Attack pour surveiller le traffic et bloquer les clients abusifs, nous allons jouer un petit sc&#233;nario. Mais d'abord, un petit point sur les cl&#233;s du cache.</p><p><strong>G&#233;n&#233;ration des cl&#233;s du cache</strong></p><p>Nous observerons dans notre sc&#233;nario deux types de cl&#233;s utilis&#233;es par Rack Attack pour communiquer avec le cache.</p><p><strong>Cl&#233;s de blocklist</strong></p><p>Les cl&#233;s des blocklists permettent de v&#233;rifier (dans notre cas) qu'une ip donn&#233;e a &#233;t&#233; marqu&#233;e dans le cache comme appartenant &#224; une blocklist.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dpvC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dpvC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png 424w, https://substackcdn.com/image/fetch/$s_!dpvC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png 848w, https://substackcdn.com/image/fetch/$s_!dpvC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png 1272w, https://substackcdn.com/image/fetch/$s_!dpvC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dpvC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png" width="1456" height="216" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:216,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17083,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dpvC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png 424w, https://substackcdn.com/image/fetch/$s_!dpvC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png 848w, https://substackcdn.com/image/fetch/$s_!dpvC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png 1272w, https://substackcdn.com/image/fetch/$s_!dpvC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fffba523a-e301-4b2d-8538-b829de2414f1_1830x272.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Cl&#233; utilis&#233;e pour consulter dans le cache la blocklist de public-post</figcaption></figure></div><p><strong>Cl&#233;s de compteur (ou cl&#233;s de filtre)</strong></p><p>Les cl&#233;s de compteur permettent &#224; Rack Attack de garder la trace du nombre de requ&#234;tes concern&#233;es par le filtre (et donc s&#233;lectionn&#233;es par le discriminant) r&#233;alis&#233;es pour une ip durant le temps d'observation.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AR-y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AR-y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png 424w, https://substackcdn.com/image/fetch/$s_!AR-y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png 848w, https://substackcdn.com/image/fetch/$s_!AR-y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png 1272w, https://substackcdn.com/image/fetch/$s_!AR-y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AR-y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png" width="1456" height="213" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:213,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:19928,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AR-y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png 424w, https://substackcdn.com/image/fetch/$s_!AR-y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png 848w, https://substackcdn.com/image/fetch/$s_!AR-y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png 1272w, https://substackcdn.com/image/fetch/$s_!AR-y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7223e2e6-5901-495b-ba23-fd075cbf12c5_1830x268.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Cl&#233; utilis&#233;e pour consulter dans le cache le compte des requ&#234;tes pass&#233;es par le filtre</figcaption></figure></div><p>Elle contient notamment une cl&#233; timestamp. Cette cl&#233; est calcul&#233;e au moment o&#249; la premi&#232;re requ&#234;te pour une ip durant la p&#233;riode d'observation est re&#231;ue :</p><pre><code><code>cl&#233; timestamp = date et heure de la premi&#232;re requ&#234;te / dur&#233;e d'observation</code></code></pre><p>La cl&#233; timestamp est donc propre &#224; une ip, et &#224; une p&#233;riode d'observation.</p><p><strong>Comprendre les interactions avec le cache</strong></p><p>Imaginons un petit sc&#233;nario pour illustrer les appels au cache de Rack Attack.</p><p>Un jeune bot na&#239;f et malveillant acc&#232;de &#224; votre plateforme dans l'intention de r&#233;cup&#233;rer les identifiants de vos utilisateurs gr&#226;ce &#224; une attaque par force brute. Il arrive sur le chemin "/sign_in", et en moins d'une seconde, tente 11 combinaisons diff&#233;rentes nom d'utilisateur - mot de passe. Au bout de la 11 &#232;me, une page d'erreur s'affiche.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mtsn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mtsn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png 424w, https://substackcdn.com/image/fetch/$s_!Mtsn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png 848w, https://substackcdn.com/image/fetch/$s_!Mtsn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png 1272w, https://substackcdn.com/image/fetch/$s_!Mtsn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mtsn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png" width="1456" height="688" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:688,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:75526,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Mtsn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png 424w, https://substackcdn.com/image/fetch/$s_!Mtsn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png 848w, https://substackcdn.com/image/fetch/$s_!Mtsn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png 1272w, https://substackcdn.com/image/fetch/$s_!Mtsn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf8a62ac-3c6f-4f7d-bc15-9eaf2f448d4b_2078x982.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Le jeune bot est stopp&#233; net : la plateforme semble hors-service. Il rentre bredouille. Pourtant, il y a actuellement de nombreux humains disciplin&#233;s qui y surfent en toute tranquillit&#233;. Comment est-ce possible ? Rembobinons.</p><p><strong>Premi&#232;re requ&#234;te</strong></p><p>Lorsque la premi&#232;re requ&#234;te est re&#231;ue par l'application (et intercept&#233;e par Rack Attack), Rack Attack consulte tout d'abord les blocklists. Il les consulte dans l'ordre dans lequel elles ont &#233;t&#233; d&#233;finies dans l'initializer.</p><p>Souvenez vous : on avait d&#233;fini dans notre configuration un filtre et une blocklist sur <strong>public-get</strong> avant celui sur public-post (dans l'initializer). &#201;tants d&#233;finis avant, ils seront consult&#233;s en premier, tout au long de ce sc&#233;nario.</p><p>Les blocklists sont consult&#233;es et aucune entr&#233;e ne correspond &#224; l'ip de notre bot malveillant, la requ&#234;te sera donc autoris&#233;e, mais il faut quand m&#234;me suivre le nombre de requ&#234;tes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uiBQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uiBQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png 424w, https://substackcdn.com/image/fetch/$s_!uiBQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png 848w, https://substackcdn.com/image/fetch/$s_!uiBQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png 1272w, https://substackcdn.com/image/fetch/$s_!uiBQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uiBQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png" width="1456" height="1112" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1112,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46000,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uiBQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png 424w, https://substackcdn.com/image/fetch/$s_!uiBQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png 848w, https://substackcdn.com/image/fetch/$s_!uiBQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png 1272w, https://substackcdn.com/image/fetch/$s_!uiBQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e9e206a-7f67-4a2b-b8ec-bf4bfd1a0e20_2048x1564.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">R&#233;ception et traitement de la premi&#232;re requ&#234;te par Rack Attack</figcaption></figure></div><p>Apr&#232;s avoir consult&#233; les blocklists, Rack Attack identifie que la requ&#234;te correspond &#224; celles filtr&#233;es par le filtre public-post : il consulte donc le compteur associ&#233;.</p><p>Cette fois encore, aucune entr&#233;e ne correspond &#224; notre bot malveillant. Rack Attack g&#233;n&#232;re donc un timestamp, puis ajoute une entr&#233;e qui contient l'ip du bot malveillant (1.2.3.4) :</p><pre><code><code>[READ] Key: rack::attack:allow2ban:ban:public-get:1.2.3.4, Result: nil</code>
<code>[READ] Key: rack::attack:allow2ban:ban:public-post:1.2.3.4, Result: nil</code>
<code>[READ] Key: rack::attack:14376799:allow2ban:count:public-post:1.2.3.4, Result: nil</code>
<code>[WRITE] Key: rack::attack:14376799:allow2ban:count:public-post:1.2.3.4, Value: 1, Expires in: 19 seconds</code>
</code></pre><p>La cl&#233; timestamp sera ensuite conserv&#233;e et utilis&#233;e pour suivre les requ&#234;tes de cette ip (qui satisfont le discriminant du filtre), jusqu&#8217;&#224; la fin du temps d&#8217;observation.</p><p><strong>Requ&#234;tes suivantes n &lt; 10</strong></p><p>Notre jeune bot na&#239;f et malveillant ne le sait pas, mais il est d&#233;sormais fich&#233;. Il continue, en toute innocence, &#224; spammer notre page de login, et rien ne semble sortir de l'ordinaire. Pourtant il est surveill&#233; de pr&#232;s.</p><p>Lorsqu'une nouvelle requ&#234;te est envoy&#233;e, Rack Attack commence &#233;galement par consulter toutes les blocklists concern&#233;es, celles-ci ne contiennent toujours pas l'ip correspondante : le jeune bot peut continuer &#224; spammer le login (pour le moment).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ie-g!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ie-g!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png 424w, https://substackcdn.com/image/fetch/$s_!Ie-g!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png 848w, https://substackcdn.com/image/fetch/$s_!Ie-g!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png 1272w, https://substackcdn.com/image/fetch/$s_!Ie-g!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ie-g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png" width="1456" height="987" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:987,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39788,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ie-g!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png 424w, https://substackcdn.com/image/fetch/$s_!Ie-g!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png 848w, https://substackcdn.com/image/fetch/$s_!Ie-g!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png 1272w, https://substackcdn.com/image/fetch/$s_!Ie-g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ea5d08-3d84-4ef6-87f1-227f1d54247b_2048x1388.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">R&#233;ception et traitements des requ&#234;tes interm&#233;diaires par Rack Attack</figcaption></figure></div><p></p><p>&#192; chacune de ses tentatives, apr&#232;s la lecture des blocklists, le compteur de public-post est incr&#233;ment&#233;, mais il n'atteint pas encore la valeur seuil attendue.</p><pre><code><code>[READ] Key: rack::attack:14376799:allow2ban:count:public-post:1.2.3.4, Result: 2</code>
<code>[WRITE] Key: rack::attack:14376799:allow2ban:count:public-post:1.2.3.4, Value: 3, Expires in: 92 seconds</code></code></pre><p><strong>10&#232;me requ&#234;te : la derni&#232;re pour notre jeune bot</strong></p><p>Lors de la dixi&#232;me tentative, les blocklists sont &#224; nouveaux consult&#233;es et ne contiennent toujours pas l'ip recherch&#233;e et laissent donc passer de nouveau cette requ&#234;te. Finiront t'elles par servir &#224; quelque chose ?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ajbM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ajbM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png 424w, https://substackcdn.com/image/fetch/$s_!ajbM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png 848w, https://substackcdn.com/image/fetch/$s_!ajbM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!ajbM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ajbM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png" width="1456" height="1183" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1183,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46604,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ajbM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png 424w, https://substackcdn.com/image/fetch/$s_!ajbM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png 848w, https://substackcdn.com/image/fetch/$s_!ajbM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png 1272w, https://substackcdn.com/image/fetch/$s_!ajbM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b3aedbb-bb35-4f63-bdac-469cd790ee24_2048x1664.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">R&#233;ception et traitement de la derni&#232;re requ&#234;te accept&#233;e par Rack Attack</figcaption></figure></div><p>Puis, le compteur de <strong>public-post </strong>est consult&#233; : il renvoie 9. Il est alors incr&#233;ment&#233; pour atteindre 10 : le maximum autoris&#233;. C'est &#224; ce moment que les choses tournent mal pour le jeune bot malveillant. Un nouvel appel en &#233;criture au cache est effectu&#233; : cette fois-ci, non pas au compteur, mais bien &#224; la blocklist de public-post. Une entr&#233;e contenant son ip y est inscrite et sera conserv&#233;e durant le BAN_TIME d&#233;fini plus haut.</p><pre><code><code>[WRITE] Key: rack::attack:allow2ban:ban:public-post:1.2.3.4, Value: 1, Expires in: 600 seconds</code></code></pre><p><em>Note : Les cl&#233;s de compteur servent exclusivement &#224; suivre le nombre de requ&#234;tes durant une p&#233;riode d'observation. La lecture d'une valeur sup&#233;rieure ou &#233;gale &#224; n-1 d&#233;clenchera l'&#233;criture d'un bannissement qui bloquera les requ&#234;tes suivantes. Mais la lecture seule du compteur, m&#234;me si elle renvoie un nombre de requ&#234;tes sup&#233;rieur &#224; celui autoris&#233;, ne permet pas de bloquer une requ&#234;te.</em></p><p><strong>11&#232;me requ&#234;te :</strong></p><p>Lors de la r&#233;ception de cette derni&#232;re requ&#234;te, Rack Attack consulte les blocklists : il consulte la blocklist de <strong>public-get</strong> sans succ&#232;s puis il consulte la blocklist de <strong>public-post</strong> : cette fois-ci, il y trouve l'ip correspondante. Rack Attack ne prend m&#234;me pas la peine de lire le compteur : cette fois-ci c'est cuit pour notre jeune bot, la requ&#234;te ne passera pas. Rack Attack envoie un simple code HTTP 503.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jJRH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jJRH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png 424w, https://substackcdn.com/image/fetch/$s_!jJRH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png 848w, https://substackcdn.com/image/fetch/$s_!jJRH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png 1272w, https://substackcdn.com/image/fetch/$s_!jJRH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jJRH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png" width="1456" height="654" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:654,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27104,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jJRH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png 424w, https://substackcdn.com/image/fetch/$s_!jJRH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png 848w, https://substackcdn.com/image/fetch/$s_!jJRH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png 1272w, https://substackcdn.com/image/fetch/$s_!jJRH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c0157c2-78b4-4cb7-96e7-7320984d11b3_2048x920.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Blocage effectu&#233; par Rack Attack</figcaption></figure></div><p>Rappelez-vous, notre jeune bot malveillant est aussi na&#239;f : il pense que la plateforme est hors-service, et s'arr&#234;te l&#224;. Il s'en va chercher une nouvelle victime.</p><p><em>Note : D&#232;s que Rack Attack re&#231;oit une r&#233;ponse positive d'une blocklist, il refuse la requ&#234;te et s'arr&#234;te l&#224;. Si l'ip du bot avait &#233;t&#233; dans la blocklist de public-get, la blocklist de public-post n'aurait m&#234;me pas &#233;t&#233; consult&#233;e pour cette requ&#234;te.</em></p><p><em>On peut &#233;galement remarquer que le compteur n'est pas consult&#233; pour cette requ&#234;te : il n'est d'ailleurs jamais bloquant. Peu importe la valeur renvoy&#233;e par le compteur, la requ&#234;te ne sera pas bloqu&#233;e sans &#233;criture dans la blocklist.</em></p><h2>Tests rapides : simuler les appels au cache</h2><p>Maintenant que nous avons compris la fa&#231;on dont Rack Attack traque et banni les requ&#234;tes abusives, nous pouvons mocker les appels et &#233;critures du cache des requ&#234;tes et v&#233;rifier que le blocage (et le d&#233;blocage) sont correctement r&#233;alis&#233;s par Rack Attack.</p><p>Au lieu de simuler l&#8217;enti&#232;ret&#233; du processus, nous allons simplement nous ins&#233;rer au niveau de la n-eme requ&#234;te, et faire croire &#224; Rack Attack qu&#8217;il s&#8217;agit bien de la n-&#232;me requ&#234;te et non de la premi&#232;re.</p><p><strong>Simuler n - 1 requ&#234;tes, envoyer r&#233;ellement la n-&#232;me et observer le blocage</strong></p><p>Nous allons mocker un appel au cache en lecture : au lieu de renvoyer nil (pas de requ&#234;te observ&#233;e) nous souhaitons qu'il renvoie 9 (d&#233;j&#224; n-1 requ&#234;tes observ&#233;es) et expect les appels au cache en &#233;criture et leurs cons&#233;quences dans nos tests. Les blocklists &#233;tant vides &#224; ce stade, il n&#8217;est pas n&#233;cessaire de les mocker, elles renverront le r&#233;sultat attendu.</p><p><em>Note : Cependant, &#233;tant donn&#233; que nous mockons un appel au cache avec des arguments sp&#233;cifiques, il faut &#233;galement expect un appel au cache aux blocklist et demander d&#8217;appeler l&#8217;original pour ne pas avoir de message d&#8217;erreur.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Xs3z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Xs3z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png 424w, https://substackcdn.com/image/fetch/$s_!Xs3z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png 848w, https://substackcdn.com/image/fetch/$s_!Xs3z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png 1272w, https://substackcdn.com/image/fetch/$s_!Xs3z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Xs3z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png" width="1456" height="726" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:726,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:273740,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Xs3z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png 424w, https://substackcdn.com/image/fetch/$s_!Xs3z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png 848w, https://substackcdn.com/image/fetch/$s_!Xs3z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png 1272w, https://substackcdn.com/image/fetch/$s_!Xs3z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36d44c7e-4677-4951-b471-9d57ea7acaf1_3609x1799.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Mock de n-1 requ&#234;tes pour Rack Attack</figcaption></figure></div><p>Ensuite on envoie la (pseudo) 10&#232;me requ&#234;te, celle-ci d&#233;clenche le bannissement. On envoie la 11&#232;me et on v&#233;rifie que l&#8217;on est banni.</p><p>Une fois impl&#233;ment&#233;, voil&#224; le r&#233;sultat :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ivg2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ivg2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png 424w, https://substackcdn.com/image/fetch/$s_!ivg2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png 848w, https://substackcdn.com/image/fetch/$s_!ivg2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png 1272w, https://substackcdn.com/image/fetch/$s_!ivg2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ivg2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png" width="1456" height="921" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:921,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:311449,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ivg2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png 424w, https://substackcdn.com/image/fetch/$s_!ivg2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png 848w, https://substackcdn.com/image/fetch/$s_!ivg2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png 1272w, https://substackcdn.com/image/fetch/$s_!ivg2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5d5bfb-3e14-40dd-b122-c33d61dfd99f_1714x1084.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Simuler un blocage, observer le d&#233;blocage</strong></p><p>C'est bon, on sait d&#233;sormais que Rack Attack bloque correctement les requ&#234;tes abusives. Il ne reste plus qu'&#224; v&#233;rifier que le d&#233;blocage est correctement effectu&#233; apr&#232;s BAN_TIME, et on a termin&#233;.</p><p>Ce que l'on souhaite maintenant, c'est s'ins&#233;rer apr&#232;s la n-&#232;me tentative et le d&#233;clenchement du blocage.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DTaY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DTaY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png 424w, https://substackcdn.com/image/fetch/$s_!DTaY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png 848w, https://substackcdn.com/image/fetch/$s_!DTaY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png 1272w, https://substackcdn.com/image/fetch/$s_!DTaY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DTaY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png" width="1456" height="538" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:538,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:152265,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DTaY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png 424w, https://substackcdn.com/image/fetch/$s_!DTaY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png 848w, https://substackcdn.com/image/fetch/$s_!DTaY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png 1272w, https://substackcdn.com/image/fetch/$s_!DTaY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F158fefc8-ae0d-4478-a7e1-581d5f464e63_3609x1333.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Mock du bannissement de Rack Attack</figcaption></figure></div><p>On impl&#233;mente cette logique :</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rui_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rui_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png 424w, https://substackcdn.com/image/fetch/$s_!rui_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png 848w, https://substackcdn.com/image/fetch/$s_!rui_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png 1272w, https://substackcdn.com/image/fetch/$s_!rui_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rui_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png" width="1456" height="856" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:856,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:239576,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rui_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png 424w, https://substackcdn.com/image/fetch/$s_!rui_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png 848w, https://substackcdn.com/image/fetch/$s_!rui_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png 1272w, https://substackcdn.com/image/fetch/$s_!rui_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73f523ea-02f2-4e19-9702-db9b4b7c8858_1598x940.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>On ajoute le setup et on obtient nos tests finaux :</p><div class="github-gist" data-attrs="{&quot;innerHTML&quot;:&quot;<div id=\&quot;gist133080166\&quot; class=\&quot;gist\&quot;>\n    <div class=\&quot;gist-file\&quot; translate=\&quot;no\&quot; data-color-mode=\&quot;light\&quot; data-light-theme=\&quot;light\&quot;>\n      <div class=\&quot;gist-data\&quot;>\n        <div class=\&quot;js-gist-file-update-container js-task-list-container\&quot;>\n  <div id=\&quot;file-optimised_tests_spec-rb\&quot; class=\&quot;file my-2\&quot;>\n    \n    <div itemprop=\&quot;text\&quot; class=\&quot;Box-body p-0 blob-wrapper data type-ruby  \&quot;>\n\n        \n<div class=\&quot;js-check-bidi js-blob-code-container blob-code-content\&quot;>\n\n  <template class=\&quot;js-file-alert-template\&quot;>\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash flash-warn flash-full d-flex flex-items-center\&quot;>\n  <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n    <span>\n      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n      <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.co/hiddenchars\&quot; target=\&quot;_blank\&quot;>Learn more about bidirectional Unicode characters</a>\n    </span>\n\n\n  <div data-view-component=\&quot;true\&quot; class=\&quot;flash-action\&quot;>        <a href=\&quot;{{ revealButtonHref }}\&quot; data-view-component=\&quot;true\&quot; class=\&quot;btn-sm btn\&quot;>    Show hidden characters\n</a>\n</div>\n</div></template>\n<template class=\&quot;js-line-alert-template\&quot;>\n  <span aria-label=\&quot;This line has hidden Unicode characters\&quot; data-view-component=\&quot;true\&quot; class=\&quot;line-alert tooltipped tooltipped-e\&quot;>\n    <svg aria-hidden=\&quot;true\&quot; height=\&quot;16\&quot; viewBox=\&quot;0 0 16 16\&quot; version=\&quot;1.1\&quot; width=\&quot;16\&quot; data-view-component=\&quot;true\&quot; class=\&quot;octicon octicon-alert\&quot;>\n    <path d=\&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\&quot;></path>\n</svg>\n</span></template>\n\n  <table data-hpc class=\&quot;highlight tab-size js-file-line-container js-code-nav-container js-tagsearch-file\&quot; data-tab-size=\&quot;8\&quot; data-paste-markdown-skip data-tagsearch-lang=\&quot;Ruby\&quot; data-tagsearch-path=\&quot;optimised_tests_spec.rb\&quot;>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L1\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;1\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC1\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-en>require</span> <span class=pl-s>&amp;quot;rails_helper&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L2\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;2\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC2\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L3\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;3\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC3\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-v>RSpec</span><span class=pl-kos>.</span><span class=pl-en>describe</span> <span class=pl-s>&amp;quot;Rack::Attack&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>type</span>: <span class=pl-pds>:request</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L4\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;4\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC4\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L5\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;5\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC5\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>before</span><span class=pl-kos>(</span><span class=pl-pds>:all</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L6\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;6\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC6\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>enabled</span> <span class=pl-c1>=</span> <span class=pl-c1>true</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L7\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;7\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC7\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>cache</span><span class=pl-kos>.</span><span class=pl-en>store</span> <span class=pl-c1>=</span> <span class=pl-v>ActiveSupport</span>::<span class=pl-v>Cache</span>::<span class=pl-v>MemoryStore</span><span class=pl-kos>.</span><span class=pl-en>new</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L8\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;8\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC8\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L9\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;9\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC9\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L10\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;10\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC10\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>after</span><span class=pl-kos>(</span><span class=pl-pds>:all</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L11\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;11\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC11\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>enabled</span> <span class=pl-c1>=</span> <span class=pl-c1>false</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L12\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;12\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC12\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L13\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;13\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC13\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L14\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;14\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC14\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>before</span><span class=pl-kos>(</span><span class=pl-pds>:each</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L15\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;15\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC15\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>cache</span><span class=pl-kos>.</span><span class=pl-en>store</span><span class=pl-kos>.</span><span class=pl-en>clear</span> <span class=pl-c># Avoid tests overlaps</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L16\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;16\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC16\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L17\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;17\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC17\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L18\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;18\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC18\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:ban_time</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>BAN_TIME</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L19\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;19\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC19\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:store</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span><span class=pl-kos>.</span><span class=pl-en>cache</span><span class=pl-kos>.</span><span class=pl-en>store</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L20\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;20\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC20\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:filters_list_regex</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-sr>/public-(post|get)/i</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L21\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;21\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC21\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:ip</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;1.2.3.4&amp;quot;</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L22\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;22\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC22\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L23\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;23\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC23\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-c># Tester que Rack Attack bloque seulement apr&#232;s MAX_ATTEMPTS tentatives</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L24\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;24\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC24\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>shared_examples</span> <span class=pl-s>&amp;quot;rate limiting and IP blocking&amp;quot;</span> <span class=pl-k>do</span> |<span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span> <span class=pl-s1>max_attempts</span><span class=pl-kos>,</span> <span class=pl-s1>filter</span>|</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L25\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;25\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC25\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:read_count_key</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-sr>/rack::attack:<span class=pl-cce>\\d</span>+:allow2ban:count:<span class=pl-s1><span class=pl-kos>#{</span><span class=pl-s1>filter</span><span class=pl-kos>}</span></span>:<span class=pl-s1><span class=pl-kos>#{</span><span class=pl-en>ip</span><span class=pl-kos>}</span></span>/</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L26\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;26\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC26\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:read_ban_key</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-sr>/rack::attack:allow2ban:ban:<span class=pl-s1><span class=pl-kos>#{</span><span class=pl-en>filters_list_regex</span><span class=pl-kos>}</span></span>:<span class=pl-s1><span class=pl-kos>#{</span><span class=pl-en>ip</span><span class=pl-kos>}</span></span>/</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L27\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;27\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC27\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L28\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;28\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC28\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>it</span> <span class=pl-s>&amp;quot;only blocks IP after MAX_ATTEMPTS <span class=pl-s1><span class=pl-kos>#{</span><span class=pl-s1>http_method</span><span class=pl-kos>}</span></span> requests&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>aggregate_failures</span>: <span class=pl-c1>true</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L29\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;29\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC29\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>store</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>to</span> <span class=pl-en>receive</span><span class=pl-kos>(</span><span class=pl-pds>:read</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>with</span><span class=pl-kos>(</span><span class=pl-en>read_count_key</span><span class=pl-kos>,</span> <span class=pl-en>anything</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>and_return</span><span class=pl-kos>(</span><span class=pl-s1>max_attempts</span> - <span class=pl-c1>1</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L30\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;30\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC30\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L31\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;31\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC31\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>store</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>to</span> <span class=pl-en>receive</span><span class=pl-kos>(</span><span class=pl-pds>:read</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>with</span><span class=pl-kos>(</span><span class=pl-en>read_ban_key</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>at_least</span><span class=pl-kos>(</span><span class=pl-pds>:once</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>and_call_original</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L32\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;32\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC32\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L33\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;33\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC33\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>public_send</span><span class=pl-kos>(</span><span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span> <span class=pl-pds>params</span>: <span class=pl-c1>nil</span><span class=pl-kos>,</span> <span class=pl-pds>headers</span>: <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;REMOTE_ADDR&amp;quot;</span> <span class=pl-c1>=&amp;gt;</span> <span class=pl-en>ip</span> <span class=pl-kos>}</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L34\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;34\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC34\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>response</span><span class=pl-kos>.</span><span class=pl-en>status</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>not_to</span> <span class=pl-en>eq</span><span class=pl-kos>(</span><span class=pl-c1>503</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L35\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;35\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC35\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L36\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;36\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC36\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>public_send</span><span class=pl-kos>(</span><span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span> <span class=pl-pds>params</span>: <span class=pl-c1>nil</span><span class=pl-kos>,</span> <span class=pl-pds>headers</span>: <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;REMOTE_ADDR&amp;quot;</span> <span class=pl-c1>=&amp;gt;</span> <span class=pl-en>ip</span> <span class=pl-kos>}</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L37\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;37\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC37\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>response</span><span class=pl-kos>.</span><span class=pl-en>status</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>to</span> <span class=pl-en>eq</span><span class=pl-kos>(</span><span class=pl-c1>503</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L38\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;38\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC38\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L39\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;39\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC39\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L40\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;40\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC40\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L41\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;41\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC41\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L42\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;42\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC42\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:ip</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;1.2.3.4&amp;quot;</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L43\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;43\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC43\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:ban_time</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>BAN_TIME</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L44\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;44\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC44\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-c># Tester que Rack Attack d&#233;bloque bien apr&#232;s BAN_TIME</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L45\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;45\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC45\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>shared_examples</span> <span class=pl-s>&amp;quot;IP unblocking&amp;quot;</span> <span class=pl-k>do</span> |<span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span> <span class=pl-s1>filter</span>|</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L46\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;46\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC46\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>let</span><span class=pl-kos>(</span><span class=pl-pds>:ban_key</span><span class=pl-kos>)</span> <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;rack::attack:allow2ban:ban:<span class=pl-s1><span class=pl-kos>#{</span><span class=pl-s1>filter</span><span class=pl-kos>}</span></span>:<span class=pl-s1><span class=pl-kos>#{</span><span class=pl-en>ip</span><span class=pl-kos>}</span></span>&amp;quot;</span> <span class=pl-kos>}</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L47\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;47\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC47\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L48\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;48\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC48\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>it</span> <span class=pl-s>&amp;quot;unblock IP after BAN_TIME for <span class=pl-s1><span class=pl-kos>#{</span><span class=pl-s1>http_method</span><span class=pl-kos>}</span></span> requests&amp;quot;</span><span class=pl-kos>,</span> <span class=pl-pds>aggregate_failures</span>: <span class=pl-c1>true</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L49\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;49\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC49\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>store</span><span class=pl-kos>.</span><span class=pl-en>write</span><span class=pl-kos>(</span><span class=pl-en>ban_key</span><span class=pl-kos>,</span> <span class=pl-c1>1</span><span class=pl-kos>,</span> <span class=pl-pds>expires_in</span>: <span class=pl-en>ban_time</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L50\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;50\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC50\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L51\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;51\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC51\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>public_send</span><span class=pl-kos>(</span><span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span> <span class=pl-pds>params</span>: <span class=pl-c1>nil</span><span class=pl-kos>,</span> <span class=pl-pds>headers</span>: <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;REMOTE_ADDR&amp;quot;</span> <span class=pl-c1>=&amp;gt;</span> <span class=pl-en>ip</span> <span class=pl-kos>}</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L52\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;52\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC52\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>response</span><span class=pl-kos>.</span><span class=pl-en>status</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>to</span> <span class=pl-en>eq</span><span class=pl-kos>(</span><span class=pl-c1>503</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L53\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;53\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC53\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L54\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;54\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC54\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>travel_to</span><span class=pl-kos>(</span><span class=pl-v>Time</span><span class=pl-kos>.</span><span class=pl-en>now</span> + <span class=pl-en>ban_time</span> + <span class=pl-c1>1</span><span class=pl-kos>.</span><span class=pl-en>minute</span><span class=pl-kos>)</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L55\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;55\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC55\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-en>public_send</span><span class=pl-kos>(</span><span class=pl-s1>http_method</span><span class=pl-kos>,</span> <span class=pl-s1>path</span><span class=pl-kos>,</span> <span class=pl-pds>params</span>: <span class=pl-c1>nil</span><span class=pl-kos>,</span> <span class=pl-pds>headers</span>: <span class=pl-kos>{</span> <span class=pl-s>&amp;quot;REMOTE_ADDR&amp;quot;</span> <span class=pl-c1>=&amp;gt;</span> <span class=pl-en>ip</span> <span class=pl-kos>}</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L56\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;56\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC56\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>        <span class=pl-en>expect</span><span class=pl-kos>(</span><span class=pl-en>response</span><span class=pl-kos>.</span><span class=pl-en>status</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>not_to</span> <span class=pl-en>eq</span><span class=pl-kos>(</span><span class=pl-c1>503</span><span class=pl-kos>)</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L57\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;57\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC57\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L58\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;58\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC58\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L59\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;59\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC59\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L60\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;60\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC60\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L61\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;61\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC61\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>describe</span> <span class=pl-s>&amp;quot;POST requests&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L62\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;62\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC62\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>context</span> <span class=pl-s>&amp;quot;when the ip is not banned&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L63\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;63\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC63\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>it_behaves_like</span> <span class=pl-s>&amp;quot;rate limiting and IP blocking&amp;quot;</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L64\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;64\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC64\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-s>&amp;quot;post&amp;quot;</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L65\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;65\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC65\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>PUBLIC_PATHS_POST</span><span class=pl-kos>.</span><span class=pl-en>first</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L66\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;66\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC66\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>MAX_ATTEMPTS_POST</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L67\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;67\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC67\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-s>&amp;quot;public-post&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L68\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;68\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC68\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L69\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;69\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC69\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L70\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;70\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC70\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>context</span> <span class=pl-s>&amp;quot;when the ip is banned&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L71\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;71\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC71\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>it_behaves_like</span> <span class=pl-s>&amp;quot;IP unblocking&amp;quot;</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L72\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;72\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC72\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-s>&amp;quot;post&amp;quot;</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L73\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;73\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC73\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>PUBLIC_PATHS_POST</span><span class=pl-kos>.</span><span class=pl-en>first</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L74\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;74\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC74\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-s>&amp;quot;public-post&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L75\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;75\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC75\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L76\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;76\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC76\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L77\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;77\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC77\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L78\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;78\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC78\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-en>describe</span> <span class=pl-s>&amp;quot;GET requests&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L79\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;79\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC79\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>context</span> <span class=pl-s>&amp;quot;when the ip is not banned&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L80\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;80\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC80\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>it_behaves_like</span> <span class=pl-s>&amp;quot;rate limiting and IP blocking&amp;quot;</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L81\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;81\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC81\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-s>&amp;quot;get&amp;quot;</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L82\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;82\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC82\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>PUBLIC_PATHS_GET</span><span class=pl-kos>.</span><span class=pl-en>first</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L83\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;83\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC83\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>MAX_ATTEMPTS_GET</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L84\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;84\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC84\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-s>&amp;quot;public-get&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L85\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;85\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC85\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L86\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;86\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC86\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>\n</td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L87\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;87\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC87\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-en>context</span> <span class=pl-s>&amp;quot;when the ip is banned&amp;quot;</span> <span class=pl-k>do</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L88\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;88\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC88\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>      <span class=pl-en>it_behaves_like</span> <span class=pl-s>&amp;quot;IP unblocking&amp;quot;</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L89\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;89\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC89\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-s>&amp;quot;get&amp;quot;</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L90\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;90\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC90\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-v>Rack</span>::<span class=pl-v>Attack</span>::<span class=pl-c1>PUBLIC_PATHS_GET</span><span class=pl-kos>.</span><span class=pl-en>first</span><span class=pl-kos>,</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L91\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;91\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC91\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>                      <span class=pl-s>&amp;quot;public-get&amp;quot;</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L92\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;92\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC92\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>    <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L93\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;93\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC93\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;>  <span class=pl-k>end</span></td>\n        </tr>\n        <tr>\n          <td id=\&quot;file-optimised_tests_spec-rb-L94\&quot; class=\&quot;blob-num js-line-number js-code-nav-line-number js-blob-rnum\&quot; data-line-number=\&quot;94\&quot;></td>\n          <td id=\&quot;file-optimised_tests_spec-rb-LC94\&quot; class=\&quot;blob-code blob-code-inner js-file-line\&quot;><span class=pl-k>end</span></td>\n        </tr>\n  </table>\n</div>\n\n\n    </div>\n\n  </div>\n</div>\n\n      </div>\n      <div class=\&quot;gist-meta\&quot;>\n        <a href=\&quot;https://gist.github.com/Piwido/6387cf89a64a85e21e42a233028842c3/raw/db6f69633fd9ba4b6eb6fa02d2d05f87588ae305/optimised_tests_spec.rb\&quot; style=\&quot;float:right\&quot; class=\&quot;Link--inTextBlock\&quot;>view raw</a>\n        <a href=\&quot;https://gist.github.com/Piwido/6387cf89a64a85e21e42a233028842c3#file-optimised_tests_spec-rb\&quot; class=\&quot;Link--inTextBlock\&quot;>\n          optimised_tests_spec.rb\n        </a>\n        hosted with &amp;#10084; by <a class=\&quot;Link--inTextBlock\&quot; href=\&quot;https://github.com\&quot;>GitHub</a>\n      </div>\n    </div>\n</div>\n&quot;,&quot;stylesheet&quot;:&quot;https://github.githubassets.com/assets/gist-embed-26d88def9f88.css&quot;}" data-component-name="GitgistToDOM"><link rel="stylesheet" href="https://github.githubassets.com/assets/gist-embed-26d88def9f88.css"><div id="gist133080166" class="gist">
    <div class="gist-file" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        <div class="js-gist-file-update-container js-task-list-container">
  <div id="file-optimised_tests_spec-rb" class="file my-2">
    
    <div itemprop="text" class="Box-body p-0 blob-wrapper data type-ruby  ">

        
<div class="js-check-bidi js-blob-code-container blob-code-content">

  
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  
    

    <span>
      This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div>

  <span data-view-component="true" class="line-alert tooltipped tooltipped-e">
    
    

</span>

  <table data-hpc="" class="highlight tab-size js-file-line-container js-code-nav-container js-tagsearch-file" data-tab-size="8" data-paste-markdown-skip="" data-tagsearch-lang="Ruby" data-tagsearch-path="optimised_tests_spec.rb">
        <tbody><tr>
          <td id="file-optimised_tests_spec-rb-L1" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-optimised_tests_spec-rb-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-en">require</span> <span class="pl-s">"rails_helper"</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L2" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-optimised_tests_spec-rb-LC2" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L3" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-optimised_tests_spec-rb-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-v">RSpec</span><span class="pl-kos">.</span><span class="pl-en">describe</span> <span class="pl-s">"Rack::Attack"</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-pds">:request</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L4" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-optimised_tests_spec-rb-LC4" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L5" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-optimised_tests_spec-rb-LC5" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">before</span><span class="pl-kos">(</span><span class="pl-pds">:all</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L6" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-optimised_tests_spec-rb-LC6" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">enabled</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L7" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-optimised_tests_spec-rb-LC7" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">store</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">Cache</span>::<span class="pl-v">MemoryStore</span><span class="pl-kos">.</span><span class="pl-en">new</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L8" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-optimised_tests_spec-rb-LC8" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L9" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-optimised_tests_spec-rb-LC9" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L10" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-optimised_tests_spec-rb-LC10" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">after</span><span class="pl-kos">(</span><span class="pl-pds">:all</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L11" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-optimised_tests_spec-rb-LC11" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">enabled</span> <span class="pl-c1">=</span> <span class="pl-c1">false</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L12" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-optimised_tests_spec-rb-LC12" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L13" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-optimised_tests_spec-rb-LC13" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L14" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-optimised_tests_spec-rb-LC14" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">before</span><span class="pl-kos">(</span><span class="pl-pds">:each</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L15" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-optimised_tests_spec-rb-LC15" class="blob-code blob-code-inner js-file-line">    <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">store</span><span class="pl-kos">.</span><span class="pl-en">clear</span> <span class="pl-c"># Avoid tests overlaps</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L16" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-optimised_tests_spec-rb-LC16" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L17" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-optimised_tests_spec-rb-LC17" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L18" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-optimised_tests_spec-rb-LC18" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:ban_time</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">BAN_TIME</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L19" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-optimised_tests_spec-rb-LC19" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:store</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">store</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L20" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="20"></td>
          <td id="file-optimised_tests_spec-rb-LC20" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:filters_list_regex</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-sr">/public-(post|get)/i</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L21" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="21"></td>
          <td id="file-optimised_tests_spec-rb-LC21" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:ip</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-s">"1.2.3.4"</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L22" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="22"></td>
          <td id="file-optimised_tests_spec-rb-LC22" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L23" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="23"></td>
          <td id="file-optimised_tests_spec-rb-LC23" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># Tester que Rack Attack bloque seulement apr&#232;s MAX_ATTEMPTS tentatives</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L24" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="24"></td>
          <td id="file-optimised_tests_spec-rb-LC24" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">shared_examples</span> <span class="pl-s">"rate limiting and IP blocking"</span> <span class="pl-k">do</span> |<span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-s1">max_attempts</span><span class="pl-kos">,</span> <span class="pl-s1">filter</span>|</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L25" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="25"></td>
          <td id="file-optimised_tests_spec-rb-LC25" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:read_count_key</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-sr">/rack::attack:<span class="pl-cce">\d</span>+:allow2ban:count:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">filter</span><span class="pl-kos">}</span></span>:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-en">ip</span><span class="pl-kos">}</span></span>/</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L26" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="26"></td>
          <td id="file-optimised_tests_spec-rb-LC26" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:read_ban_key</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-sr">/rack::attack:allow2ban:ban:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-en">filters_list_regex</span><span class="pl-kos">}</span></span>:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-en">ip</span><span class="pl-kos">}</span></span>/</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L27" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="27"></td>
          <td id="file-optimised_tests_spec-rb-LC27" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L28" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="28"></td>
          <td id="file-optimised_tests_spec-rb-LC28" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">it</span> <span class="pl-s">"only blocks IP after MAX_ATTEMPTS <span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">http_method</span><span class="pl-kos">}</span></span> requests"</span><span class="pl-kos">,</span> <span class="pl-pds">aggregate_failures</span>: <span class="pl-c1">true</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L29" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="29"></td>
          <td id="file-optimised_tests_spec-rb-LC29" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">store</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to</span> <span class="pl-en">receive</span><span class="pl-kos">(</span><span class="pl-pds">:read</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">with</span><span class="pl-kos">(</span><span class="pl-en">read_count_key</span><span class="pl-kos">,</span> <span class="pl-en">anything</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">and_return</span><span class="pl-kos">(</span><span class="pl-s1">max_attempts</span> - <span class="pl-c1">1</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L30" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="30"></td>
          <td id="file-optimised_tests_spec-rb-LC30" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L31" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="31"></td>
          <td id="file-optimised_tests_spec-rb-LC31" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">store</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to</span> <span class="pl-en">receive</span><span class="pl-kos">(</span><span class="pl-pds">:read</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">with</span><span class="pl-kos">(</span><span class="pl-en">read_ban_key</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">at_least</span><span class="pl-kos">(</span><span class="pl-pds">:once</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">and_call_original</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L32" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="32"></td>
          <td id="file-optimised_tests_spec-rb-LC32" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L33" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="33"></td>
          <td id="file-optimised_tests_spec-rb-LC33" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">public_send</span><span class="pl-kos">(</span><span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-pds">params</span>: <span class="pl-c1">nil</span><span class="pl-kos">,</span> <span class="pl-pds">headers</span>: <span class="pl-kos">{</span> <span class="pl-s">"REMOTE_ADDR"</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">ip</span> <span class="pl-kos">}</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L34" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="34"></td>
          <td id="file-optimised_tests_spec-rb-LC34" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">status</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">not_to</span> <span class="pl-en">eq</span><span class="pl-kos">(</span><span class="pl-c1">503</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L35" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="35"></td>
          <td id="file-optimised_tests_spec-rb-LC35" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L36" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="36"></td>
          <td id="file-optimised_tests_spec-rb-LC36" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">public_send</span><span class="pl-kos">(</span><span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-pds">params</span>: <span class="pl-c1">nil</span><span class="pl-kos">,</span> <span class="pl-pds">headers</span>: <span class="pl-kos">{</span> <span class="pl-s">"REMOTE_ADDR"</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">ip</span> <span class="pl-kos">}</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L37" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="37"></td>
          <td id="file-optimised_tests_spec-rb-LC37" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">status</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to</span> <span class="pl-en">eq</span><span class="pl-kos">(</span><span class="pl-c1">503</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L38" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="38"></td>
          <td id="file-optimised_tests_spec-rb-LC38" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L39" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="39"></td>
          <td id="file-optimised_tests_spec-rb-LC39" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L40" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="40"></td>
          <td id="file-optimised_tests_spec-rb-LC40" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L41" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="41"></td>
          <td id="file-optimised_tests_spec-rb-LC41" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L42" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="42"></td>
          <td id="file-optimised_tests_spec-rb-LC42" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:ip</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-s">"1.2.3.4"</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L43" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="43"></td>
          <td id="file-optimised_tests_spec-rb-LC43" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:ban_time</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">BAN_TIME</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L44" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="44"></td>
          <td id="file-optimised_tests_spec-rb-LC44" class="blob-code blob-code-inner js-file-line">  <span class="pl-c"># Tester que Rack Attack d&#233;bloque bien apr&#232;s BAN_TIME</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L45" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="45"></td>
          <td id="file-optimised_tests_spec-rb-LC45" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">shared_examples</span> <span class="pl-s">"IP unblocking"</span> <span class="pl-k">do</span> |<span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-s1">filter</span>|</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L46" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="46"></td>
          <td id="file-optimised_tests_spec-rb-LC46" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">let</span><span class="pl-kos">(</span><span class="pl-pds">:ban_key</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-s">"rack::attack:allow2ban:ban:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">filter</span><span class="pl-kos">}</span></span>:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-en">ip</span><span class="pl-kos">}</span></span>"</span> <span class="pl-kos">}</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L47" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="47"></td>
          <td id="file-optimised_tests_spec-rb-LC47" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L48" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="48"></td>
          <td id="file-optimised_tests_spec-rb-LC48" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">it</span> <span class="pl-s">"unblock IP after BAN_TIME for <span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">http_method</span><span class="pl-kos">}</span></span> requests"</span><span class="pl-kos">,</span> <span class="pl-pds">aggregate_failures</span>: <span class="pl-c1">true</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L49" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="49"></td>
          <td id="file-optimised_tests_spec-rb-LC49" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">store</span><span class="pl-kos">.</span><span class="pl-en">write</span><span class="pl-kos">(</span><span class="pl-en">ban_key</span><span class="pl-kos">,</span> <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-pds">expires_in</span>: <span class="pl-en">ban_time</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L50" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="50"></td>
          <td id="file-optimised_tests_spec-rb-LC50" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L51" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="51"></td>
          <td id="file-optimised_tests_spec-rb-LC51" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">public_send</span><span class="pl-kos">(</span><span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-pds">params</span>: <span class="pl-c1">nil</span><span class="pl-kos">,</span> <span class="pl-pds">headers</span>: <span class="pl-kos">{</span> <span class="pl-s">"REMOTE_ADDR"</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">ip</span> <span class="pl-kos">}</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L52" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="52"></td>
          <td id="file-optimised_tests_spec-rb-LC52" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">status</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to</span> <span class="pl-en">eq</span><span class="pl-kos">(</span><span class="pl-c1">503</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L53" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="53"></td>
          <td id="file-optimised_tests_spec-rb-LC53" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L54" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="54"></td>
          <td id="file-optimised_tests_spec-rb-LC54" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">travel_to</span><span class="pl-kos">(</span><span class="pl-v">Time</span><span class="pl-kos">.</span><span class="pl-en">now</span> + <span class="pl-en">ban_time</span> + <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">minute</span><span class="pl-kos">)</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L55" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="55"></td>
          <td id="file-optimised_tests_spec-rb-LC55" class="blob-code blob-code-inner js-file-line">        <span class="pl-en">public_send</span><span class="pl-kos">(</span><span class="pl-s1">http_method</span><span class="pl-kos">,</span> <span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-pds">params</span>: <span class="pl-c1">nil</span><span class="pl-kos">,</span> <span class="pl-pds">headers</span>: <span class="pl-kos">{</span> <span class="pl-s">"REMOTE_ADDR"</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">ip</span> <span class="pl-kos">}</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L56" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="56"></td>
          <td id="file-optimised_tests_spec-rb-LC56" class="blob-code blob-code-inner js-file-line">        <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">status</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">not_to</span> <span class="pl-en">eq</span><span class="pl-kos">(</span><span class="pl-c1">503</span><span class="pl-kos">)</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L57" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="57"></td>
          <td id="file-optimised_tests_spec-rb-LC57" class="blob-code blob-code-inner js-file-line">      <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L58" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="58"></td>
          <td id="file-optimised_tests_spec-rb-LC58" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L59" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="59"></td>
          <td id="file-optimised_tests_spec-rb-LC59" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L60" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="60"></td>
          <td id="file-optimised_tests_spec-rb-LC60" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L61" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="61"></td>
          <td id="file-optimised_tests_spec-rb-LC61" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">describe</span> <span class="pl-s">"POST requests"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L62" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="62"></td>
          <td id="file-optimised_tests_spec-rb-LC62" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">context</span> <span class="pl-s">"when the ip is not banned"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L63" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="63"></td>
          <td id="file-optimised_tests_spec-rb-LC63" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">it_behaves_like</span> <span class="pl-s">"rate limiting and IP blocking"</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L64" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="64"></td>
          <td id="file-optimised_tests_spec-rb-LC64" class="blob-code blob-code-inner js-file-line">                      <span class="pl-s">"post"</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L65" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="65"></td>
          <td id="file-optimised_tests_spec-rb-LC65" class="blob-code blob-code-inner js-file-line">                      <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">PUBLIC_PATHS_POST</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L66" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="66"></td>
          <td id="file-optimised_tests_spec-rb-LC66" class="blob-code blob-code-inner js-file-line">                      <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">MAX_ATTEMPTS_POST</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L67" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="67"></td>
          <td id="file-optimised_tests_spec-rb-LC67" class="blob-code blob-code-inner js-file-line">                      <span class="pl-s">"public-post"</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L68" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="68"></td>
          <td id="file-optimised_tests_spec-rb-LC68" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L69" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="69"></td>
          <td id="file-optimised_tests_spec-rb-LC69" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L70" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="70"></td>
          <td id="file-optimised_tests_spec-rb-LC70" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">context</span> <span class="pl-s">"when the ip is banned"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L71" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="71"></td>
          <td id="file-optimised_tests_spec-rb-LC71" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">it_behaves_like</span> <span class="pl-s">"IP unblocking"</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L72" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="72"></td>
          <td id="file-optimised_tests_spec-rb-LC72" class="blob-code blob-code-inner js-file-line">                      <span class="pl-s">"post"</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L73" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="73"></td>
          <td id="file-optimised_tests_spec-rb-LC73" class="blob-code blob-code-inner js-file-line">                      <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">PUBLIC_PATHS_POST</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L74" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="74"></td>
          <td id="file-optimised_tests_spec-rb-LC74" class="blob-code blob-code-inner js-file-line">                      <span class="pl-s">"public-post"</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L75" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="75"></td>
          <td id="file-optimised_tests_spec-rb-LC75" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L76" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="76"></td>
          <td id="file-optimised_tests_spec-rb-LC76" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L77" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="77"></td>
          <td id="file-optimised_tests_spec-rb-LC77" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L78" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="78"></td>
          <td id="file-optimised_tests_spec-rb-LC78" class="blob-code blob-code-inner js-file-line">  <span class="pl-en">describe</span> <span class="pl-s">"GET requests"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L79" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="79"></td>
          <td id="file-optimised_tests_spec-rb-LC79" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">context</span> <span class="pl-s">"when the ip is not banned"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L80" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="80"></td>
          <td id="file-optimised_tests_spec-rb-LC80" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">it_behaves_like</span> <span class="pl-s">"rate limiting and IP blocking"</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L81" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="81"></td>
          <td id="file-optimised_tests_spec-rb-LC81" class="blob-code blob-code-inner js-file-line">                      <span class="pl-s">"get"</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L82" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="82"></td>
          <td id="file-optimised_tests_spec-rb-LC82" class="blob-code blob-code-inner js-file-line">                      <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">PUBLIC_PATHS_GET</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L83" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="83"></td>
          <td id="file-optimised_tests_spec-rb-LC83" class="blob-code blob-code-inner js-file-line">                      <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">MAX_ATTEMPTS_GET</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L84" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="84"></td>
          <td id="file-optimised_tests_spec-rb-LC84" class="blob-code blob-code-inner js-file-line">                      <span class="pl-s">"public-get"</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L85" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="85"></td>
          <td id="file-optimised_tests_spec-rb-LC85" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L86" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="86"></td>
          <td id="file-optimised_tests_spec-rb-LC86" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L87" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="87"></td>
          <td id="file-optimised_tests_spec-rb-LC87" class="blob-code blob-code-inner js-file-line">    <span class="pl-en">context</span> <span class="pl-s">"when the ip is banned"</span> <span class="pl-k">do</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L88" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="88"></td>
          <td id="file-optimised_tests_spec-rb-LC88" class="blob-code blob-code-inner js-file-line">      <span class="pl-en">it_behaves_like</span> <span class="pl-s">"IP unblocking"</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L89" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="89"></td>
          <td id="file-optimised_tests_spec-rb-LC89" class="blob-code blob-code-inner js-file-line">                      <span class="pl-s">"get"</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L90" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="90"></td>
          <td id="file-optimised_tests_spec-rb-LC90" class="blob-code blob-code-inner js-file-line">                      <span class="pl-v">Rack</span>::<span class="pl-v">Attack</span>::<span class="pl-c1">PUBLIC_PATHS_GET</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">,</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L91" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="91"></td>
          <td id="file-optimised_tests_spec-rb-LC91" class="blob-code blob-code-inner js-file-line">                      <span class="pl-s">"public-get"</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L92" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="92"></td>
          <td id="file-optimised_tests_spec-rb-LC92" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L93" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="93"></td>
          <td id="file-optimised_tests_spec-rb-LC93" class="blob-code blob-code-inner js-file-line">  <span class="pl-k">end</span></td>
        </tr>
        <tr>
          <td id="file-optimised_tests_spec-rb-L94" class="blob-num js-line-number js-code-nav-line-number js-blob-rnum" data-line-number="94"></td>
          <td id="file-optimised_tests_spec-rb-LC94" class="blob-code blob-code-inner js-file-line"><span class="pl-k">end</span></td>
        </tr>
  </tbody></table>
</div>


    </div>

  </div>
</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/Piwido/6387cf89a64a85e21e42a233028842c3/raw/db6f69633fd9ba4b6eb6fa02d2d05f87588ae305/optimised_tests_spec.rb" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/Piwido/6387cf89a64a85e21e42a233028842c3#file-optimised_tests_spec-rb" class="Link--inTextBlock">
          optimised_tests_spec.rb
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>
</div><p>Tadam ! D&#233;sormais, peu importe la configuration que vous choisissez (notamment des p&#233;riodes d'observation plus longues avec par cons&#233;quent beaucoup de requ&#234;tes autoris&#233;es) vos tests auront une dur&#233;e minimale et invariable.</p><h1>Quelques recommandations pour la fin</h1><p>Bon, je vais quand m&#234;me vous laisser avec quelques recommandations utiles, notamment si vous souhaitez &#233;tendre la configuration propos&#233;e dans cette article &#224; d'autres types de requ&#234;tes.</p><ul><li><p>Chaque filtre, throttle ou blocklist d&#233;fini dans l'initializer doit avoir un nom unique pour &#234;tre correctement trait&#233; par Rack Attack.</p></li><li><p>Utiliser un cache s&#233;par&#233; du cache qui g&#232;re les fonctionnalit&#233;s de l'application, afin de ne pas surcharger ce dernier.</p></li><li><p>Eviter les filtres trop g&#233;n&#233;raux, couvrant trop de routes ou qui traquent abusivement les requ&#234;tes GET : vous ralentiriez le traitement des requ&#234;tes concern&#233;es pour un gain moindre.</p></li><li><p>D&#233;sactiver Rack Attack en environnement de test, et ne l'activer que pour le fichier de test Rack Attack.</p></li><li><p>Attention aux bots de r&#233;f&#233;rencement : adaptez votre nombre de tentatives autoris&#233;es, &#233;vitez les filtres trop s&#233;v&#232;res sur les requ&#234;tes GET ou <a href="https://www.rubybiscuit.fr/p/utiliser-la-session-de-rails-sur">utilisez un site diff&#233;rent pour la vitrine (r&#233;f&#233;rencement) et la logique application</a>.</p></li></ul><p>&#8212; <em>Ines</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.rubybiscuit.fr/subscribe?&quot;,&quot;text&quot;:&quot;Abonnez-vous maintenant&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.rubybiscuit.fr/subscribe?"><span>Abonnez-vous maintenant</span></a></p>]]></content:encoded></item><item><title><![CDATA[🍪🦄 Pourquoi autant de licornes françaises sont codées en Ruby ?]]></title><description><![CDATA[Temps de lecture : 5 minutes]]></description><link>https://www.rubybiscuit.fr/p/pourquoi-autant-de-licornes-francaises</link><guid isPermaLink="false">https://www.rubybiscuit.fr/p/pourquoi-autant-de-licornes-francaises</guid><pubDate>Wed, 04 Sep 2024 14:00:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2e6426ab-08f5-4851-9a18-1c5845ddb91e_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello les petits Biscuits !</p><p>Bienvenue sur la 25&#232;me &#233;dition de Ruby Biscuit.<br>Vous &#234;tes maintenant 501 abonn&#233;s &#129395;</p><p>Maintenant Ruby biscuit, c&#8217;est aussi votre meilleur alli&#233; pour recruter des devs Ruby !<br>Si vous n&#8217;avez pas encore rejoint le club, RDV sur <a href="https://recrutement.rubybiscuit.fr">https://recrutement.rubybiscuit.fr</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w5Nb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp" width="1456" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:68128,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w5Nb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 424w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 848w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1272w, https://substackcdn.com/image/fetch/$s_!w5Nb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c3b7e4a-7073-4d44-a57d-2a795d97a63e_1456x808.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Bonne lecture.</p><div><hr></div><h2><strong>Pourquoi autant de licornes fran&#231;aises sont cod&#233;es en Ruby ?</strong></h2><p>Il est de notori&#233;t&#233; publique que le Ruby on Rails est utilis&#233; par de merveilleuses entreprises &#224; travers notre merveilleuse plan&#232;te. On pense &#233;videmment &#224; <strong>Github, Shopify, Twitch, Airbnb, Coinbase</strong>, ...</p><p>En France aussi le RoR est tr&#232;s utilis&#233; dans les startups mais un constat est particuli&#232;rement frappant : la pr&#233;sence du Ruby on Rails dans les licornes* du web fran&#231;ais.</p><p><em>*Licorne : entreprise dont la valorisation d&#233;passe 1 milliard de dollars.</em></p><p>Parmi les licornes fran&#231;aises plusieurs (et parmi les plus importantes) sont cod&#233;es en RoR : <strong>Doctolib, Pennylane, Sorare, Qonto, Aircall et</strong> <strong>Swile</strong>.</p><p>Comment expliquer <strong>cette insolente r&#233;ussite</strong> du RoR alors que les Cassandre du javascript annoncent sa fin chaque ann&#233;e ? ^^</p><p>Tout d'abord il ne s'agit pas d'une hype momentan&#233;e de ce brillant langage (jeu de mot avec le Ruby qui brille ;)), car ces entreprises n'ont pas &#233;t&#233; cr&#233;&#233;es au m&#234;me moment. Voici <strong>leurs dates de cr&#233;ation</strong> :</p><ul><li><p>Doctolib : 2013</p></li><li><p>Aircall : 2014</p></li><li><p>Qonto : 2016</p></li><li><p>Sorare et Swile : 2018</p></li><li><p>Pennylane : 2020</p></li></ul><p>Alors vous me direz :</p><blockquote><p>Oui mais aucune n'a &#233;t&#233; cr&#233;&#233;e tout r&#233;cemment !</p></blockquote><p>Et je vous r&#233;pondrai que vous avez raison, mais comme aurait pu le dire Simone de Beauvoir :</p><blockquote><p>On ne na&#238;t pas Licorne, on le devient.</p></blockquote><p>Par exemple <strong>Doctolib</strong> a vu sa corne pousser en 2019, suite &#224; sa lev&#233;e de 150M&#8364;, soit 6 ans apr&#232;s sa cr&#233;ation.</p><p>Ainsi je ne doute pas que, dans quelques ann&#233;es, il y aura de belles entreprises RoR cr&#233;&#233;es autour de l'ann&#233;e 2024.</p><p>Mais alors <strong>pourquoi autant de licornes fran&#231;aises sont cod&#233;es en Ruby on Rails ?</strong><em>Sans parler de toutes les boites &#224; succ&#232;s qui ne sont pas des licornes</em></p><p>D'apr&#232;s ce John Rogue, sur Quora, Doctolib a choisi le Ruby <strong>par hasard</strong>, parce que c'&#233;tait un langage &#224; la mode &#224; cette &#233;poque (utilisant au passage une belle locution latine rappelant que "le hasard fa&#231;onne le monde").</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HZNE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HZNE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HZNE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HZNE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HZNE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HZNE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg" width="1456" height="737" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:737,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:266692,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HZNE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg 424w, https://substackcdn.com/image/fetch/$s_!HZNE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg 848w, https://substackcdn.com/image/fetch/$s_!HZNE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!HZNE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd51c7b4b-ccd6-4199-99f9-d7d05ce992a4_2760x1398.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>C'est un peu court comme explication, mais ce n'est pas compl&#232;tement faux.</p><p>Oui il est crucial, si l'on veut d&#233;velopper une technologie ambitieuse, d'utiliser une <strong>technologie populaire</strong>. Une licorne a besoin de beaucoup de d&#233;veloppeurs pour (se) d&#233;velopper et le choix ne peut pas &#234;tre que sur les capacit&#233;s techniques d'une techno.</p><p>Autrement dit, si le langage A est 5% plus performant pour le besoin que le langage B, mais qu'il y a 2 fois plus de d&#233;veloppeurs du langage B, alors ce dernier para&#238;t bien plus adapt&#233;.</p><h1>Ruby on Rails = V&#233;locit&#233;</h1><p>D&#233;velopper une startup qui va cartonner c'est hyper complexe. Il y a beaucoup d'appel&#233;s et peu d'&#233;lus. Une fois que l'on a trouv&#233; un product market fit (une fois que son produit rencontre la demande), on se rend compte que des dizaines d'entreprises sont sur le m&#234;me cr&#233;neau en train de cravacher pour &#234;tre la meilleure. Et dans ce cas il faut <strong>&#234;tre hyper rapide</strong>.</p><p>Cela tombe bien c'est <strong>un des gros avantages du RoR</strong>.</p><p>Comparons la v&#233;locit&#233; du <strong>RoR versus Django (Python) et Express.js (Node.js)</strong>, ses principaux rivaux &#9876;&#65039; On pourrait aussi choisir Laravel ou Symphony qui sont des frameworks populaires de PHP, mais &#224; ma connaissance, ces options reviennent moins souvent, en ce moment, quand il faut construire une plateforme web complexe.</p><p>Voici les points de comparaison que je retiens :</p><ul><li><p><strong>Philosophie et Structure</strong></p></li><li><p><strong>Outils et Scaffolding</strong></p></li><li><p><strong>&#201;cosyst&#232;me et Packages</strong></p></li><li><p><strong>Courbe d'apprentissage</strong></p></li></ul><h2>1- Philosophie et Structure</h2><ul><li><p><strong>Ruby on Rails</strong> : Suivant la philosophie <strong>"Convention over Configuration"</strong>, le RoR impose des conventions strictes qui permettent de d&#233;marrer rapidement un projet sans avoir &#224; prendre de nombreuses d&#233;cisions de configuration &#10230; Gain de temps assur&#233;. Surtout si vous avez des d&#233;vs qui aiment d&#233;battre &#128556;</p></li><li><p><strong>Django</strong> : privil&#233;gie une approche <strong>"Explicit is Better Than Implicit"</strong>, o&#249; chaque &#233;l&#233;ment doit &#234;tre d&#233;fini clairement. Cette approche apporte de la clart&#233; et une structure robuste, mais n&#233;cessite plus de configuration initiale.</p></li><li><p><strong>Express.js</strong> : Node.js offre une grande flexibilit&#233; et <strong>libert&#233; de structuration</strong>. Avec Express.js, un cadre minimaliste, les d&#233;veloppeurs ont plus de libert&#233; pour organiser le projet comme ils le souhaitent. Cependant, cette flexibilit&#233; peut entra&#238;ner un temps de configuration plus long compar&#233; au RoR et Django.</p></li></ul><p><strong>Synth&#232;se</strong> :</p><ul><li><p>&#128007;&#128007;&#128007; RoR</p></li><li><p>&#128007;&#128007; Django</p></li><li><p>&#128007; Express.js</p></li></ul><h3><strong>&#8220;Vous n&#8217;&#234;tes pas un flocon de neige magnifique et unique&#8221;</strong></h3><p>Voici ce qu'on peut lire sur le site de <a href="https://rubyonrails.org/doctrine/fr#convention-over-configuration">Rails</a> concernant la Convention :</p><blockquote><p>L&#8217;un des premiers slogans de Rails &#233;tait : <strong>&#8220;Vous n&#8217;&#234;tes pas un flocon de neige magnifique et unique&#8221;</strong>. La devise dit qu&#8217;en abandonnant l&#8217;individualit&#233;, il est possible d&#8217;&#233;viter de r&#233;soudre des probl&#232;mes triviaux et d&#8217;avancer plus rapidement dans les domaines qui sont vraiment importants.</p><p>Qui se soucie du format dans lequel vos cl&#233;s primaires sont d&#233;crites dans la base de donn&#233;es ? Est-ce vraiment important si c&#8217;est &#8220;id&#8221;, &#8220;postId&#8221;, &#8220;posts_id&#8221; ou &#8220;pid&#8221; ? Cette solution m&#233;rite-t-elle une discussion constante ? Non</p></blockquote><h2><strong>2- Outils et Scaffolding</strong></h2><ul><li><p><strong>Ruby on Rails</strong> : dispose d'outils de scaffolding puissants qui permettent de g&#233;n&#233;rer rapidement des mod&#232;les, des vues, et des contr&#244;leurs. Cela acc&#233;l&#232;re significativement la mise en place des fonctionnalit&#233;s de base d'une application.</p></li><li><p><strong>Django</strong> : propose &#233;galement des outils de scaffolding, mais ils sont moins int&#233;gr&#233;s et complets que ceux de RoR. Le d&#233;veloppement initial peut donc &#234;tre plus lent en comparaison.</p></li><li><p><strong>Express.js</strong> : offre des g&#233;n&#233;rateurs de code, mais ceux-ci sont g&#233;n&#233;ralement moins robustes et moins int&#233;gr&#233;s que ceux de RoR et Django. Les d&#233;veloppeurs doivent souvent configurer manuellement davantage d'&#233;l&#233;ments.</p></li></ul><p><strong>Synth&#232;se</strong> :</p><ul><li><p>&#128007;&#128007;&#128007; RoR</p></li><li><p>&#128007;&#128007; Django</p></li><li><p>&#128007; Express.js</p></li></ul><h3><strong>Exemple de scaffolding avec RoR : la g&#233;n&#233;ration d'une ressource compl&#232;te (Mod&#232;le, Contr&#244;leur, Vues, Routes)</strong></h3><p>Supposons que vous souhaitiez cr&#233;er une application qui g&#232;re des articles de blog. Avec Rails, vous pouvez g&#233;n&#233;rer un mod&#232;le, un contr&#244;leur, et les vues associ&#233;es en une seule commande :</p><pre><code><code>rails generate scaffold Article title:string content:text published:boolean</code></code></pre><p>Ce que cette commande fait :</p><ul><li><p><strong>Mod&#232;le (</strong><code>Article</code><strong>)</strong> : Cr&#233;e un mod&#232;le <code>Article</code> avec les attributs <code>title</code> (cha&#238;ne de caract&#232;res), <code>content</code> (texte), et <code>published</code> (bool&#233;en).</p></li><li><p><strong>Contr&#244;leur (</strong><code>ArticlesController</code><strong>)</strong> : Cr&#233;e un contr&#244;leur <code>ArticlesController</code> avec les actions standard CRUD (Create, Read, Update, Delete) : <code>index</code>, <code>show</code>, <code>new</code>, <code>edit</code>, <code>create</code>, <code>update</code>, <code>destroy</code>.</p></li><li><p><strong>Vues</strong> : G&#233;n&#232;re les vues associ&#233;es &#224; chaque action du contr&#244;leur (<code>index.html.erb</code>, <code>show.html.erb</code>, <code>new.html.erb</code>, <code>edit.html.erb</code>, etc.).</p></li><li><p><strong>Routes</strong> : Ajoute automatiquement les routes RESTful associ&#233;es dans le fichier <code>config/routes.rb</code>, comme <code>/articles</code>, <code>/articles/:id</code>, <code>/articles/new</code>, etc.</p></li><li><p><strong>Migration</strong> : Cr&#233;e une migration pour cr&#233;er la table <code>articles</code> dans la base de donn&#233;es avec les colonnes <code>title</code>, <code>content</code>, et <code>published</code>.</p></li></ul><h2><strong>3- &#201;cosyst&#232;me et Packages</strong></h2><ul><li><p><strong>Ruby on Rails </strong>: dispose d'un &#233;cosyst&#232;me mature avec de nombreuses gemmes bien maintenues, permettant d'ajouter rapidement des fonctionnalit&#233;s. L'int&#233;gration des gemmes est g&#233;n&#233;ralement fluide, ce qui acc&#233;l&#232;re le d&#233;veloppement. Et beaucoup de d&#233;veloppeurs, notamment en France, choisissent cette techno pour sa communaut&#233; active et bienveillante.</p></li><li><p><strong>Django</strong> : b&#233;n&#233;ficie &#233;galement d'un &#233;cosyst&#232;me riche de packages, avec un fort accent sur les meilleures pratiques de s&#233;curit&#233; et de structure. Les packages sont bien int&#233;gr&#233;s et maintenus, mais l'&#233;cosyst&#232;me est l&#233;g&#232;rement moins vaste que celui d'Express.js.</p></li><li><p><strong>Express.js</strong> : via npm, poss&#232;de le plus grand &#233;cosyst&#232;me de packages, offrant une flexibilit&#233; immense. Cependant, cette richesse peut parfois entra&#238;ner des probl&#232;mes de compatibilit&#233; ou de maintenance, n&#233;cessitant un temps suppl&#233;mentaire pour la s&#233;lection et l'int&#233;gration des packages.</p></li><li><p>&#128007; &#128007;Express.js</p></li><li><p>&#128007;&#128007; RoR</p></li><li><p>&#128007; Django</p></li></ul><h3>La puissance du Wagon</h3><p>Dans le contexte fran&#231;ais, un autre &#233;l&#233;ment &#224; prendre en compte est l'existence du bootcamp Le Wagon. Chaque mois ce sont plus de cent personnes qui sont dipl&#244;m&#233;es en d&#233;veloppement web en Ruby on Rails, puisque le Wagon forme au web &#224; travers ce framework.</p><p>Evidemment, nombreux sont les alumni qui ne finissent pas d&#233;veloppeurs mais ces personnes ont aussi un impact puisqu'un fondateur d'entreprise pr&#233;f&#232;re que son CTO utilise une technologie qu'il comprend plut&#244;t qu'une autre. En tant que dirigeant d'une agence travaillant en Ruby on Rails, j'ai eu plusieurs clients qui pr&#233;f&#233;raient travailler en RoR simplement du fait qu'ils aient fait le Wagon.</p><p>Bien-s&#251;r les projets ambitieux ont surtout besoin de d&#233;veloppeurs exp&#233;riment&#233;s et l'on ne devient pas d&#233;veloppeur en 9 semaines. Mais Le Wagon est une &#233;cole formidable, qui donne le go&#251;t du d&#233;veloppement et qui a form&#233; des juniors qui sont d&#233;sormais des s&#233;niors, je le vois au quotidien chez Capsens.</p><h2><strong>4- Courbe d'apprentissage</strong></h2><ul><li><p><strong>Ruby on Rails (RoR)</strong> : RoR est souvent consid&#233;r&#233; comme ayant une courbe d'apprentissage douce, gr&#226;ce &#224; sa documentation compl&#232;te et &#224; ses conventions strictes et claires. Cela permet aux d&#233;veloppeurs de devenir rapidement productifs. Cela explique notamment pourquoi le Wagon a choisi d'enseigner le web &#224; travers le Rails</p></li><li><p><strong>Django</strong> : Django offre une excellente documentation et une communaut&#233; active, ce qui en fait un framework accessible. Sa philosophie "Explicit is Better Than Implicit" aide &#224; comprendre clairement ce qui se passe dans le code, mais cela peut n&#233;cessiter un peu plus de temps pour ma&#238;triser certains concepts.</p></li><li><p><strong>Node.js (avec Express.js)</strong> : Node.js n&#233;cessite une bonne compr&#233;hension de JavaScript, notamment des concepts asynchrones comme les callbacks, les promesses, et async/await. La flexibilit&#233; et l'asynchronisme de Node.js peuvent ralentir la productivit&#233; initiale, surtout pour les d&#233;veloppeurs moins exp&#233;riment&#233;s.</p></li></ul><p>Ce qui est fort avec le Ruby on Rails, c'est que l'on peut recruter d'excellents d&#233;veloppeurs dans d'autres technologies et les former rapidement &#224; sa stack.</p><ul><li><p>&#128007;&#128007;&#128007; RoR</p></li><li><p>&#128007;&#128007;&#128007; Django</p></li><li><p>&#128007; Express.js</p></li></ul><h2>RoR n'est pas seul</h2><p>Evidemment, pour un projet web complexe qui scale, le Ruby est souvent coupl&#233; d'autres technologies. En terme de back Qonto a d&#233;velopp&#233; le coeur de son syst&#232;me bancaire en Go, tandis que les fonctionnalit&#233;s utilisateurs sont en RoR. Idem pour Sorare qui couple le Ruby on Rails au Go.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6mch!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6mch!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!6mch!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!6mch!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!6mch!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6mch!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:264808,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6mch!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!6mch!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!6mch!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!6mch!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93718df8-3b24-4a4c-9bd4-f283e1833fda_1600x900.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Screenshot de la page Welcome to the Jungle de Qonto, partie technique</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TJMw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TJMw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png 424w, https://substackcdn.com/image/fetch/$s_!TJMw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png 848w, https://substackcdn.com/image/fetch/$s_!TJMw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png 1272w, https://substackcdn.com/image/fetch/$s_!TJMw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TJMw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png" width="1456" height="706" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:706,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:131146,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TJMw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png 424w, https://substackcdn.com/image/fetch/$s_!TJMw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png 848w, https://substackcdn.com/image/fetch/$s_!TJMw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png 1272w, https://substackcdn.com/image/fetch/$s_!TJMw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e05c31-d00f-4780-99ca-f20fe0d791c7_1822x884.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Screenshot de la page Welcome to the Jungle de Sorare, partie technique</figcaption></figure></div><p>Chez Doctolib le Ruby on Rails est vraiment roi. Coupl&#233; en front au React (avec React Native pour le mobile). Ce duo de choc on le retrouve dans beaucoup d'apps importantes (parce qu'on peut dire que React a vraiment pris une longueur d'avance en terme de front en France). Il est dr&#244;le de voir que notre stack chez Capsens est identique &#224; celle-ci : RoR + React + Kubernetes/Docker + AWS</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bc2Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bc2Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png 424w, https://substackcdn.com/image/fetch/$s_!bc2Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png 848w, https://substackcdn.com/image/fetch/$s_!bc2Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png 1272w, https://substackcdn.com/image/fetch/$s_!bc2Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bc2Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png" width="1456" height="693" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:693,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:157001,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bc2Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png 424w, https://substackcdn.com/image/fetch/$s_!bc2Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png 848w, https://substackcdn.com/image/fetch/$s_!bc2Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png 1272w, https://substackcdn.com/image/fetch/$s_!bc2Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbac586ee-2751-41e9-a036-30387e4ead2b_1866x888.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>D'ailleurs pour la science voici les langages backend que j'ai recens&#233; chez les principales licornes fran&#231;aises du web :</p><ul><li><p><strong>C# / .NET :</strong> Pigment</p></li><li><p><strong>Java</strong> : Dataiku, Mirakl,</p></li><li><p><strong>Tr&#232;s diversifi&#233;</strong> (Plusieurs parmi Symphony + Scala + Node + Go + Python + Java) : Contentsquare, ManoMano, Blablacar, Vestiaire Collective, Deezer</p></li><li><p><strong>Go</strong> : Lydia</p></li><li><p><strong>Python (Flask)</strong> : Alan</p></li><li><p><strong>Python (Django) + Scala </strong>: BackMarket</p></li></ul><h1>Scaler avec Ruby</h1><h2>Le t&#233;moignage de Pennylane</h2><blockquote><p>Le RoR c'est bien pour d&#233;marrer vite mais &#231;a ne scale pas</p></blockquote><p>Cette r&#233;plique on l'a beaucoup entendue il y a plusieurs ann&#233;es. Depuis Ruby n'a eu de cesse d'innover particuli&#232;rement pour mieux g&#233;rer le scaling.</p><p>Dans cet article <a href="https://medium.com/pennylane-engineering/scaling-our-ruby-on-rails-monolith-using-packwerk-part-1-b787aaa218ff">https://medium.com/pennylane-engineering/scaling-our-ruby-on-rails-monolith-using-packwerk-part-1-b787aaa218ff</a>, Alexandre Ruban, ex-Pennylane, explique le scaling avec Ruby chez Pennylane.</p><p>Tout d'abord il pose les bases :</p><ul><li><p>Pennylane a mis en place une &#233;quipe de 120 d&#233;v Ruby en 3 ans &#128558;</p></li><li><p>400 commits, soit 45 000 nouvelles lignes par semaine &#129327;</p></li><li><p>Ils ont donc connu un probl&#232;me de scaling avec une base de code trop grosse et monolithique &#129335;</p></li></ul><p>Il explique que la principale solution a &#233;t&#233; de <strong>diviser l'app en services</strong> afin qu'elle soit plus compr&#233;hensible par les d&#233;veloppeurs. De plus Pennylane a utilis&#233; un outil, <strong><a href="https://github.com/Shopify/packwerk">Packwerk</a>,</strong> mis en ligne par Shopify qui les a aid&#233; &#224; rendre leur app plus modulaire (vive <strong>la communaut&#233; RoR</strong> !).</p><p>Et voil&#224; ! </p><p><em>Concr&#232;tement ce n'est pas si simple, mais ce n'est jamais simple de scaler une app complexe, quelque soit le langage.</em></p><h2>Ruby on Rails s'est r&#233;invent&#233; pour scaler</h2><p>Ruby et Ruby on Rails ont &#233;t&#233; am&#233;lior&#233;s pour r&#233;pondre aux <strong>besoins de scalabilit&#233;</strong> et de performance. Voici les principales am&#233;liorations de ces derni&#232;res ann&#233;es :</p><ul><li><p>Ruby a introduit <strong>la compilation JIT</strong> (Just in time) dans sa version 2.6 (2019), et optimis&#233; son gestionnaire de m&#233;moire pour am&#233;liorer la r&#233;activit&#233;.</p></li><li><p>Rails a ajout&#233; <strong><a href="https://ai.rails-guide.com/fr/api_app.html">un mode API</a></strong> plus l&#233;ger, optimis&#233; les requ&#234;tes de base de donn&#233;es, et adopt&#233; des pratiques de modularisation comme les microservices.</p></li><li><p>Rails a &#233;galement renforc&#233; son &#233;cosyst&#232;me avec des outils comme <strong>Active Job</strong> pour le traitement en arri&#232;re-plan</p></li></ul><p>Il existe de nombreuses comparaisons des performances de Ruby par rapport &#224; d'autres langages. Les conclusions sont souvent que Ruby est &#224; peu pr&#232;s aussi performant que ses concurrents mais que pour faire de la performance pure il ne faut pas s'orienter vers des technologies web de type Rails/Node/Django mais vers du Go ou du Scala. Ce qui explique pourquoi beaucoup d&#8217;entreprises qui scalent combinent plusieurs langages.</p><h1>En conclusion</h1><p>Je ne vous cache pas <strong>mon bonheur de voir que le Ruby on Rails soit autant repr&#233;sent&#233;</strong> dans de belles et grandes entreprises fran&#231;aises o&#249; le d&#233;veloppement est central, mais <strong>cela ne m'&#233;tonne pas</strong>. Le Ruby on Rails permet d'aller vite, a des conventions strictes qui &#233;vitent de se prendre la t&#234;te pour rien, et une communaut&#233; incroyable. Cette techno a tout pour plaire et a l&#8217;avenir devant elle.</p><p>A partir du moment o&#249; d'aussi grandes entreprises font du Ruby, cela cr&#233;e <strong>un effet boule de neige</strong> car elles recrutent et forment en Ruby, ce qui pousse plus de d&#233;veloppeurs &#224; se former &#224; cette techno et la communaut&#233; s&#8217;&#233;toffe encore.</p><p>Ce qui est paradoxal, c'est que le Ruby on Rails est aussi (et surtout) <strong>un excellent framework pour bootstrapper</strong> (= d&#233;marrer sans avoir beaucoup de moyens financiers) un projet et pour des entreprises petites ou moyennes. Car la plupart des projets n'auront jamais &#224; scaler au point de devenir des licornes. Mais on est jamais &#224; l'abri d'un succ&#232;s fulgurant &#128640;</p><p>N&#8217;h&#233;sitez pas &#224; r&#233;agir &#224; cet article en r&#233;ponse &#224; ce mail ou dans les commentaires ;)</p><p>Vive le Ruby &#10084;&#65039;</p><p>&#8212; <em>Nicolas</em></p>]]></content:encoded></item></channel></rss>