<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Martin Catty - Articles</title>
  <id>https://martin.catty.fr/blog/</id>
  <link href="https://martin.catty.fr/blog/" rel="alternate"/>
  <link href="https://martin.catty.fr/blog/atom.xml" rel="self"/>
  <updated>2026-09-30T00:00:00Z</updated>
  <entry>
    <title>De ingress-nginx à Envoy Gateway</title>
    <id>https://martin.catty.fr/blog/de-ingress-nginx-a-envoy-gateway/</id>
    <link href="https://martin.catty.fr/blog/de-ingress-nginx-a-envoy-gateway/" rel="alternate"/>
    <updated>2026-09-30T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Il y a trois ans, je vous expliquais comment &lt;a href="https://martin.catty.fr/blog/authentifiez-les-services-derriere-vos-ingress-avec-oauth2-proxy/"&gt;protéger les services derrière vos ingress avec OAuth2 Proxy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Trois annotations sur un ingress nginx, un proxy installé une fois dans le cluster, et le tour était joué.&lt;/p&gt;
&lt;p&gt;Aujourd'hui, nous avons retiré ingress-nginx de tous nos clusters. Et oauth2-proxy avec.&lt;/p&gt;
&lt;p&gt;Sur le papier, une migration d'ingress c'est « juste » réécrire des manifests. En pratique, c'est surtout découvrir tout ce que nginx faisait pour nous sans qu'on le lui demande.&lt;/p&gt;
&lt;p&gt;Cet article raconte cette migration, et surtout ce qui a cassé.&lt;/p&gt;
&lt;h3&gt;Pourquoi migrer ?&lt;/h3&gt;
&lt;p&gt;La raison la plus évidente : ingress-nginx n'est plus maintenu. Le projet est passé en maintenance minimale puis s'est arrêté en mars 2026. Garder un composant exposé sur Internet sans correctifs de sécurité n'est pas une option.&lt;/p&gt;
&lt;p&gt;Mais c'est aussi l'occasion de régler un problème plus ancien.&lt;/p&gt;
&lt;p&gt;L'objet &lt;code&gt;Ingress&lt;/code&gt; de Kubernetes est très pauvre. Il sait router un host et un chemin vers un service, et c'est à peu près tout. Tout le reste (taille des requêtes, timeouts, authentification, websockets, sessions persistantes…) passe par des annotations propres à chaque contrôleur :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;nginx.ingress.kubernetes.io/proxy-body-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;nginx.ingress.kubernetes.io/proxy-read-timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3600"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;nginx.ingress.kubernetes.io/affinity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cookie&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;nginx.ingress.kubernetes.io/auth-url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://url-du-proxy/oauth2/auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ces annotations sont devenues une API de fait : des chaînes de caractères, non typées, non validées, et qui ne veulent rien dire pour un autre contrôleur.&lt;/p&gt;
&lt;p&gt;La Gateway API corrige ça avec un modèle en plusieurs objets, chacun porté par un rôle différent :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GatewayClass&lt;/code&gt; : quelle implémentation (ici Envoy Gateway), gérée par l'équipe plateforme&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Gateway&lt;/code&gt; : un point d'entrée, ses listeners et ses ports&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPRoute&lt;/code&gt; : le routage d'une application, géré par l'équipe qui la déploie&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et pour tout ce que le standard ne couvre pas encore, Envoy Gateway propose des policies typées qui s'attachent à une &lt;code&gt;Gateway&lt;/code&gt; ou à une &lt;code&gt;HTTPRoute&lt;/code&gt;. Nous allons en croiser plusieurs.&lt;/p&gt;
&lt;h3&gt;Pourquoi Envoy Gateway et pas Cilium ?&lt;/h3&gt;
&lt;p&gt;Nos clusters utilisent déjà Cilium comme CNI, et Cilium implémente la Gateway API. Le choix semblait évident.&lt;/p&gt;
&lt;p&gt;Il ne l'a pas été, pour une raison très concrète : les sessions persistantes.&lt;/p&gt;
&lt;p&gt;Une de nos applications est stateful côté serveur. Un utilisateur doit toujours retomber sur le même pod, sinon il perd sa session. Avec nginx, c'était l'annotation &lt;code&gt;affinity: cookie&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dans la Gateway API, cette fonctionnalité existe (&lt;code&gt;sessionPersistence&lt;/code&gt;, &lt;a href="https://gateway-api.sigs.k8s.io/geps/gep-1619/"&gt;GEP-1619&lt;/a&gt;), mais seulement dans le canal &lt;code&gt;experimental&lt;/code&gt; des CRDs. Au moment où nous avons fait le choix, Cilium ne l'implémentait pas contrairement à Envoy Gateway.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mon-app&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;parentRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;edge&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;hostnames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mon-app.exemple.fr&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backendRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mon-app&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;8080&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sessionPersistence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;sessionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mon_app_sticky&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Cookie&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;absoluteTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;48h&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;cookieConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;lifetimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Permanent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Il faut donc installer les CRDs du canal &lt;code&gt;experimental&lt;/code&gt;, et accepter que ces champs puissent encore évoluer. C'est un compromis que nous avons fait en connaissance de cause.&lt;/p&gt;
&lt;h3&gt;Installer sans se tirer une balle dans le pied&lt;/h3&gt;
&lt;p&gt;Tout est déployé en GitOps avec Argo CD. Nous avons séparé l'installation en trois applications distinctes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;les CRDs (Gateway API et Envoy Gateway)&lt;/li&gt;
&lt;li&gt;le contrôleur Envoy Gateway&lt;/li&gt;
&lt;li&gt;les objets propres au cluster : &lt;code&gt;GatewayClass&lt;/code&gt;, &lt;code&gt;Gateway&lt;/code&gt;, &lt;code&gt;EnvoyProxy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La raison est simple : si le contrôleur et les objets qui dépendent des CRDs arrivent dans la même synchronisation, Argo CD peut tenter de créer une &lt;code&gt;Gateway&lt;/code&gt; avant que son type existe. Séparer les applications (et ajouter des sync waves) garantit l'ordre.&lt;/p&gt;
&lt;p&gt;Ce découpage a un autre intérêt, plus important à mes yeux : il suit le modèle de rôles de Gateway API. Là où une ressource &lt;code&gt;Ingress&lt;/code&gt; mélangeait tout (le choix du contrôleur, le TLS, le routage et des annotations propres à nginx), Gateway API sépare les responsabilités :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;la &lt;code&gt;GatewayClass&lt;/code&gt; et l'&lt;code&gt;EnvoyProxy&lt;/code&gt; : comment le trafic entre dans le cluster, c'est l'affaire de la plateforme&lt;/li&gt;
&lt;li&gt;la &lt;code&gt;Gateway&lt;/code&gt; : les points d'entrée, les listeners, le TLS, toujours côté plateforme&lt;/li&gt;
&lt;li&gt;les &lt;code&gt;HTTPRoute&lt;/code&gt; et les policies attachées à une route : le routage d'une application, qui appartient à l'équipe qui la déploie&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans une logique plateforme, c'est exactement ce que nous cherchions. L'équipe infra possède les trois applications Argo CD ci-dessus. Les équipes produit gardent la main sur les couches hautes : leurs &lt;code&gt;HTTPRoute&lt;/code&gt;, leurs timeouts, leur authentification, versionnés avec leur application. Elles n'ont pas besoin de toucher à la &lt;code&gt;Gateway&lt;/code&gt; pour publier un nouveau chemin, et une erreur dans une route n'impacte pas les autres.&lt;/p&gt;
&lt;p&gt;La frontière se règle sur la &lt;code&gt;Gateway&lt;/code&gt; elle-même, avec &lt;code&gt;allowedRoutes&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;allowedRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;namespaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;All&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;from: All&lt;/code&gt; laisse n'importe quel namespace s'attacher. Pour être plus strict, &lt;code&gt;from: Selector&lt;/code&gt; limite l'attachement aux namespaces portant un label choisi par la plateforme.&lt;/p&gt;
&lt;h3&gt;Exposer Envoy : un NodePort derrière Traefik&lt;/h3&gt;
&lt;p&gt;Nos clusters avaient déjà un point d'entrée : un load balancer, puis des Traefik sur des VM, hors des clusters, qui terminent le TLS et répartissent le trafic vers les nœuds.&lt;/p&gt;
&lt;p&gt;Nous l'avons gardé tel quel. Envoy est simplement exposé en &lt;code&gt;NodePort&lt;/code&gt; : le même port est ouvert sur chaque nœud, et Traefik a tous les nœuds comme backends.&lt;/p&gt;
&lt;p&gt;C'est le modèle standard pour Kubernetes hors cloud. C'est d'ailleurs ce que font les load balancers des clouds : un NLB AWS en mode instance ne fait rien d'autre que viser un NodePort sur chaque nœud.&lt;/p&gt;
&lt;p&gt;Avec Envoy Gateway, le service est piloté par un objet &lt;code&gt;EnvoyProxy&lt;/code&gt;, référencé par la &lt;code&gt;GatewayClass&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EnvoyProxy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway-nodeport&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;edge&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Kubernetes&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;kubernetes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;envoyService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;StrategicMerge&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;NodePort&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;externalTrafficPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Cluster&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10080&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;nodePort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;30080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;externalTrafficPolicy: Cluster&lt;/code&gt; est un choix délibéré. Avec cette valeur, n'importe quel nœud accepte la connexion et la renvoie vers un pod Envoy, éventuellement sur un autre nœud. On paie un saut réseau de plus, mais chaque nœud est un backend valide pour Traefik, même s'il n'héberge aucun Envoy. Un health check simple suffit.&lt;/p&gt;
&lt;p&gt;Avec &lt;code&gt;Local&lt;/code&gt;, on économiserait ce saut, mais un nœud sans pod Envoy refuserait le trafic. Traefik devrait alors savoir, à tout moment, quels nœuds hébergent un Envoy.&lt;/p&gt;
&lt;h3&gt;Retrouver l'IP du client&lt;/h3&gt;
&lt;p&gt;Deux proxys en cascade entrainent une conséquence directe : Envoy ne voit plus l'IP du client, mais celle de Traefik. Pour retrouver la vraie IP, il faut lui dire de faire confiance à un saut dans &lt;code&gt;X-Forwarded-For&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ClientTrafficPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway-trusted-proxy-headers&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;edge&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;targetRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Gateway&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;clientIPDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;xForwardedFor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;numTrustedHops&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sans ça, tous vos logs d'accès, vos rate limits et vos listes d'IP autorisées voient la même adresse. Et avec une valeur trop grande, n'importe quel client peut forger son IP en ajoutant un en-tête &lt;code&gt;X-Forwarded-For&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Mais ce réglage repose sur une hypothèse forte : seul Traefik parle à Envoy. Si un client peut joindre Envoy directement, c'est lui qui écrit le dernier &lt;code&gt;X-Forwarded-For&lt;/code&gt;, et Envoy le croira sur parole.&lt;/p&gt;
&lt;h3&gt;Ne laisser entrer que Traefik&lt;/h3&gt;
&lt;p&gt;Or un NodePort écoute sur toutes les interfaces de tous les nœuds. Sans filtrage, n'importe quelle machine du réseau peut joindre Envoy sur le port 30080, en contournant tout ce que fait Traefik (TLS, rate limiting, listes d'IP), et en choisissant l'IP qu'elle veut faire croire.&lt;/p&gt;
&lt;p&gt;Le premier réflexe serait une &lt;code&gt;NetworkPolicy&lt;/code&gt; sur les pods Envoy, qui n'accepte que les IP des Traefik. Avec &lt;code&gt;externalTrafficPolicy: Cluster&lt;/code&gt;, ça ne fonctionne pas : quand un nœud renvoie la connexion vers un Envoy situé sur un autre nœud, il fait du SNAT. Le pod voit arriver l'IP du nœud, plus celle de Traefik.&lt;/p&gt;
&lt;p&gt;Il faut donc filtrer avant la répartition, sur l'hôte lui-même. Cilium sait le faire avec son firewall hôte (&lt;code&gt;hostFirewall.enabled=true&lt;/code&gt;) et une policy qui sélectionne des nœuds plutôt que des pods :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cilium.io/v2&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;CiliumClusterwideNetworkPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;host-nodeport-envoy&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;nodeSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fromCIDR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10.0.0.15/32&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10.0.1.15/32&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;toPorts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"30080"&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;TCP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Attention au piège : dès qu'une policy hôte sélectionne un nœud, celui-ci passe en refus par défaut pour tout le trafic entrant. SSH, API server, kubelet, etcd, tunnel VXLAN, health checks de Cilium : tout ce qui n'est pas explicitement autorisé est coupé.&lt;/p&gt;
&lt;p&gt;Nous avons donc écrit la policy complète (le trafic intra-cluster via &lt;code&gt;fromEntities: [cluster, remote-node, health]&lt;/code&gt;, puis les ports d'administration), et l'avons d'abord déployée avec &lt;code&gt;policyAuditMode&lt;/code&gt; activé.&lt;/p&gt;
&lt;p&gt;Dans ce mode, Cilium laisse tout passer mais journalise ce qu'il aurait bloqué. Quelques jours de &lt;a href="https://docs.cilium.io/en/stable/observability/hubble/"&gt;Hubble&lt;/a&gt; plus tard, plus rien d'inattendu n'apparaissait, et nous avons activé l'application réelle.&lt;/p&gt;
&lt;p&gt;Au passage, on corrige aussi la faiblesse du &lt;code&gt;X-Forwarded-For&lt;/code&gt; vue plus haut : seuls les Traefik peuvent désormais joindre Envoy, donc le dernier saut est toujours un proxy de confiance.&lt;/p&gt;
&lt;h3&gt;Basculer sans big bang&lt;/h3&gt;
&lt;p&gt;Pour chaque cluster, nous avons fait tourner nginx et Envoy en parallèle. Les applications étaient d'abord exposées via Envoy sur une URL différente, testées, puis nous avons basculé le vrai nom et retiré l'ingress.&lt;/p&gt;
&lt;p&gt;C'est plus long, mais c'est ce qui nous a permis de découvrir les problèmes qui suivent pendant notre QA, avant qu'ils n'atteignent les utilisateurs.&lt;/p&gt;
&lt;h3&gt;Les uploads&lt;/h3&gt;
&lt;p&gt;C'est le premier point sur lequel nous sommes tombés pendant la QA : sur l'URL de test servie par Envoy, l'envoi de gros fichiers échouait, alors qu'il passait sans problème par nginx.&lt;/p&gt;
&lt;p&gt;Côté nginx, nous avions &lt;code&gt;proxy-body-size: 0&lt;/code&gt;, c'est-à-dire pas de limite. Et nginx a une particularité : quand le corps d'une requête dépasse son buffer mémoire, il l'écrit dans un fichier temporaire sur disque. Il peut donc absorber un fichier de plusieurs gigaoctets sans exploser en mémoire.&lt;/p&gt;
&lt;p&gt;Envoy a une philosophie différente. Il est pensé pour streamer : il fait passer les octets au fil de l'eau et applique une limite de buffer par connexion (32 Ko par défaut) pour gérer le contrôle de flux. Tant que rien n'a besoin du corps complet, ça marche très bien.&lt;/p&gt;
&lt;p&gt;Concrètement, quand le backend lit moins vite que le client n'envoie, Envoy accumule jusqu'à cette limite, puis arrête de lire la socket du client le temps que ça se vide. Le client ralentit, la mémoire reste stable. Un fichier de 5 Go traverse Envoy sans jamais occuper plus de quelques dizaines de kilo-octets.&lt;/p&gt;
&lt;p&gt;Le problème apparaît dès qu'un élément de la chaîne a besoin du corps complet avant de le laisser passer. Là, la limite n'est plus un seuil de contrôle de flux, c'est un plafond. Au-delà, la requête est rejetée avec un &lt;code&gt;413 Payload Too Large&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Restait à trouver qui, dans notre chaîne, réclamait le corps complet.&lt;/p&gt;
&lt;h4&gt;Le coupable : notre WAF&lt;/h4&gt;
&lt;p&gt;Nous avions profité de la migration pour ajouter un WAF, &lt;a href="https://coraza.io/"&gt;Coraza&lt;/a&gt;, chargé dans Envoy sous forme de module Wasm, avec les règles OWASP CRS. Il était en mode &lt;code&gt;DetectionOnly&lt;/code&gt; : il journalise ce qu'il aurait bloqué, mais ne bloque rien. Du moins, c'est ce que nous pensions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EnvoyExtensionPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;coraza-waf&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;edge&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;targetRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Gateway&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;wasm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;coraza-waf&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;rootID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;waf&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;failOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Image&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ghcr.io/corazawaf/coraza-proxy-wasm:0.6.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;default_directives&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;default&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;directives_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Include @recommended-conf&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Include @crs-setup-conf&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Include @owasp_crs/*.conf&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecRuleEngine DetectionOnly&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecRequestBodyAccess On&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour inspecter le corps d'une requête (chercher une injection SQL dans un formulaire, par exemple), un WAF doit l'avoir en entier. C'est ce que fait &lt;code&gt;SecRequestBodyAccess On&lt;/code&gt; : Coraza demande à Envoy de bufferiser le corps avant de le transmettre au backend. C'est exactement le cas qui transforme la limite de buffer en plafond.&lt;/p&gt;
&lt;p&gt;Et il y a un deuxième étage. La configuration recommandée de Coraza fixe &lt;code&gt;SecRequestBodyInMemoryLimit&lt;/code&gt; à 128 Kio, avec &lt;code&gt;SecRequestBodyLimitAction Reject&lt;/code&gt;. Au-delà de 128 Kio, la requête est rejetée.&lt;/p&gt;
&lt;p&gt;Le détail qui tue : ce rejet ne passe pas par le moteur de règles. Il s'applique donc même en &lt;code&gt;DetectionOnly&lt;/code&gt;. Notre WAF « en observation » bloquait tous les uploads de plus de 128 Kio.&lt;/p&gt;
&lt;h4&gt;La mauvaise réponse&lt;/h4&gt;
&lt;p&gt;Le réflexion instinctif serait de monter la limite de buffer d'Envoy, dans la &lt;code&gt;ClientTrafficPolicy&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;bufferLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ça fonctionne, en partie : Envoy accepte de garder de gros corps en mémoire. Mais c'est une très mauvaise idée.&lt;/p&gt;
&lt;p&gt;Cette limite s'applique par connexion, à toutes les routes du cluster. Quelques uploads simultanés de gros fichiers, ou un client rapide face à un backend lent, et un pod Envoy peut consommer plusieurs gigaoctets avant d'être tué pour dépassement de mémoire. Or tout le trafic du cluster passe par ces pods. Pour comparaison, le guide d'Envoy pour un proxy exposé en bordure de réseau recommande de rester à 32 Ko.&lt;/p&gt;
&lt;p&gt;Et pourquoi ne pas faire comme nginx, en écrivant sur disque ? Envoy a bien un filtre pour ça (&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/file_system_buffer_filter"&gt;&lt;code&gt;file_system_buffer&lt;/code&gt;&lt;/a&gt;), mais &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/file_system_buffer/v3/file_system_buffer.proto"&gt;sa documentation&lt;/a&gt; le dit explicitement : travail en cours, pas destiné à la production. Envoy Gateway ne l'expose d'ailleurs pas.&lt;/p&gt;
&lt;h4&gt;La bonne réponse&lt;/h4&gt;
&lt;p&gt;Le problème n'était pas la taille du buffer, mais le fait que quelqu'un réclame le corps complet. Il suffit de dire à Coraza d'inspecter le début du corps puis de laisser passer le reste :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecRequestBodyAccess On&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecRequestBodyLimitAction ProcessPartial&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Avec &lt;code&gt;ProcessPartial&lt;/code&gt;, Coraza analyse les 128 premiers Kio et laisse filer la suite sans la retenir. Les requêtes classiques (formulaires, JSON d'API) restent inspectées en entier, et les uploads repassent en streaming.&lt;/p&gt;
&lt;p&gt;Ce compromis a un prix, et il faut le dire : un attaquant peut remplir les 128 premiers Kio avec du contenu anodin, puis placer sa charge après. Coraza inspecte le début, ne voit rien, et laisse passer le reste sans l'avoir lu. C'est un contournement de WAF bien connu.&lt;/p&gt;
&lt;p&gt;En &lt;code&gt;DetectionOnly&lt;/code&gt;, c'est acceptable : le WAF ne bloque rien de toute façon, il nous sert à observer. Mais ce n'est pas la configuration à garder au moment de passer en blocage.&lt;/p&gt;
&lt;p&gt;La cible est de ne pas traiter toutes les routes de la même façon. Les injections SQL, XSS ou de commandes passent par des champs de formulaire ou du JSON, qui font quelques kilo-octets. Un corps de plusieurs centaines de kilo-octets sur une route qui n'attend pas de fichier est déjà suspect en soi. D'où :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;une inspection stricte par défaut (&lt;code&gt;Reject&lt;/code&gt;), avec une &lt;code&gt;EnvoyExtensionPolicy&lt;/code&gt; attachée à la &lt;code&gt;Gateway&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;une &lt;code&gt;EnvoyExtensionPolicy&lt;/code&gt; dédiée, attachée aux seules &lt;code&gt;HTTPRoute&lt;/code&gt; d'upload, en &lt;code&gt;ProcessPartial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;sur ces routes d'upload, une protection portée par l'application : type de fichier, taille maximale, analyse antivirus&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La limite de buffer d'Envoy est redescendue à une valeur raisonnable, et la mémoire des pods Envoy ne dépend plus de la taille des fichiers envoyés.&lt;/p&gt;
&lt;p&gt;La leçon dépasse le WAF. Avec Envoy, quand un gros corps de requête pose problème, la bonne question n'est pas « quelle limite monter ? », mais « quel filtre a besoin du corps complet, et en a-t-il vraiment besoin ? ».&lt;/p&gt;
&lt;p&gt;Un ext_authz qui transmet le corps au service d'autorisation, un filtre Lua ou un autre module Wasm peuvent produire exactement le même effet.&lt;/p&gt;
&lt;h3&gt;Les timeouts qui ne veulent pas dire la même chose&lt;/h3&gt;
&lt;p&gt;Deuxième surprise, sur des téléchargements longs et des traitements côté serveur qui prennent plusieurs minutes.&lt;/p&gt;
&lt;p&gt;Chez nginx, &lt;code&gt;proxy_read_timeout&lt;/code&gt; est un délai entre deux lectures : tant que le backend envoie des octets régulièrement, la connexion vit.&lt;/p&gt;
&lt;p&gt;Chez Envoy, le timeout de route est une durée totale : le temps laissé au backend pour envoyer la réponse complète, à partir du moment où la requête a été entièrement reçue. Et sa valeur par défaut est de 15 secondes.&lt;/p&gt;
&lt;p&gt;Un export qui met deux minutes à se générer ? Coupé à 15 secondes, avec un &lt;code&gt;504&lt;/code&gt; et un &lt;code&gt;upstream request timeout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La Gateway API permet de le régler par règle de route :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backendRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mon-app&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;8080&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;timeouts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5m&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;backendRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Plutôt que de monter la valeur globalement, nous l'avons fait route par route, là où le besoin était réel. Un timeout long partout, c'est aussi des connexions qui s'accumulent quand un backend est lent.&lt;/p&gt;
&lt;h3&gt;Websockets et protocoles maison&lt;/h3&gt;
&lt;p&gt;Avec nginx, les websockets fonctionnaient avec deux annotations de timeout. Avec Envoy, un upgrade de connexion n'est accepté que s'il est explicitement autorisé.&lt;/p&gt;
&lt;p&gt;Pour une application de bureau à distance en HTML5, qui garde une connexion websocket ouverte pendant toute la session :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;BackendTrafficPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;remote-desktop-session&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;targetRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;remote-desktop&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;httpUpgrade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;websocket&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;requestTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0s&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;connectionIdleTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3600s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;requestTimeout: 0s&lt;/code&gt; désactive le timeout total (une session peut durer des heures), et &lt;code&gt;connectionIdleTimeout&lt;/code&gt; coupe les sessions réellement inactives.&lt;/p&gt;
&lt;p&gt;Le cas le plus intéressant a été Headscale, l'implémentation open source du serveur de contrôle Tailscale. Le client Tailscale ne fait pas un upgrade websocket : il demande un upgrade vers son propre protocole, &lt;code&gt;tailscale-control-protocol&lt;/code&gt;, pour établir un canal chiffré avec le protocole Noise.&lt;/p&gt;
&lt;p&gt;Nginx laissait passer n'importe quel en-tête &lt;code&gt;Upgrade&lt;/code&gt;. Envoy le refuse tant qu'on ne l'a pas déclaré :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;httpUpgrade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;websocket&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tailscale-control-protocol&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le symptôme, lui, n'avait rien d'évident : des clients incapables de s'enregistrer, et rien de parlant côté application. C'est typiquement le genre de comportement implicite qu'on ne soupçonne pas avant de changer de proxy.&lt;/p&gt;
&lt;h3&gt;Adieu oauth2-proxy&lt;/h3&gt;
&lt;p&gt;C'est la partie qui m'a fait le plus plaisir.&lt;/p&gt;
&lt;p&gt;Envoy sait faire de l'OIDC nativement. L'authentification se déclare dans une &lt;code&gt;SecurityPolicy&lt;/code&gt; attachée à une route :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecurityPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mon-app-oidc&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;targetRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mon-app&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"https://login.microsoftonline.com/${tenant_id}/v2.0"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"${client_id}"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mon-app-oidc&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;openid&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;email&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;profile&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;cookieConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Lax&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Par rapport à l'approche de mon article de 2023, plusieurs choses changent :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;plus de service tiers à déployer, superviser et mettre à jour&lt;/li&gt;
&lt;li&gt;l'authentification est déclarée par route, et non par une chaîne d'annotations qui pointe vers un autre service&lt;/li&gt;
&lt;li&gt;chaque application a son propre client OIDC, donc ses propres droits. Avec oauth2-proxy, la portée du cookie (&lt;code&gt;*.recette.bar.fr&lt;/code&gt; dans l'exemple de l'époque) donnait accès à tous les services du domaine d'un coup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un piège, quand même, et pas des moindres.&lt;/p&gt;
&lt;p&gt;Par défaut, Envoy chiffre les jetons qu'il stocke dans les cookies. Or un navigateur limite un cookie à environ 4096 octets (nom et valeur compris).&lt;/p&gt;
&lt;p&gt;Chez certains utilisateurs, dont le jeton d'accès contient beaucoup de claims (des groupes, par exemple), le cookie chiffré dépasse cette taille. Le navigateur l'ignore silencieusement, et l'utilisateur tourne en boucle sur la mire de login.&lt;/p&gt;
&lt;p&gt;Seuls certains comptes étaient touchés, ce qui rend le diagnostic assez pénible.&lt;/p&gt;
&lt;p&gt;C'est une limite structurelle de l'implémentation OIDC d'Envoy Gateway : elle ne connaît que le cookie comme mécanisme de session. oauth2-proxy, lui, propose plusieurs backends de stockage, dont Redis : le cookie ne contient plus alors qu'un identifiant de session, et le jeton complet reste côté serveur. La taille du jeton devient indolore, au prix d'un service avec état à opérer, celui-là même que nous cherchions à retirer.&lt;/p&gt;
&lt;p&gt;Pour des services internes, nous avons désactivé le chiffrement des jetons (les cookies restent &lt;code&gt;HttpOnly&lt;/code&gt;) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;disableTokenEncryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;C'est un compromis. Sur un service exposé publiquement, je préférerais réduire la taille du jeton côté fournisseur d'identité (moins de claims) plutôt que de désactiver le chiffrement.&lt;/p&gt;
&lt;h3&gt;EnvoyPatchPolicy : la porte de sortie à manier avec précaution&lt;/h3&gt;
&lt;p&gt;Quand une policy ne couvre pas un besoin, Envoy Gateway propose une échappatoire : &lt;code&gt;EnvoyPatchPolicy&lt;/code&gt;. Elle permet d'appliquer un JSON Patch directement sur la configuration xDS générée pour Envoy.&lt;/p&gt;
&lt;p&gt;C'est puissant, et c'est exactement le problème.&lt;/p&gt;
&lt;p&gt;Les noms des ressources xDS sont générés par le contrôleur, et peuvent changer d'une version à l'autre. Un patch écrit à la main s'appuie sur une structure que personne ne garantit. Et surtout, il s'applique après toutes les autres policies.&lt;/p&gt;
&lt;p&gt;Nous en avons fait l'expérience : un patch de type &lt;code&gt;replace&lt;/code&gt; sur les routes d'un listener a écrasé la configuration générée par une &lt;code&gt;SecurityPolicy&lt;/code&gt; OIDC. Rien dans les objets Kubernetes ne le laissait voir : la &lt;code&gt;SecurityPolicy&lt;/code&gt; était acceptée, son statut était correct, mais la configuration réellement envoyée à Envoy ne la contenait plus.&lt;/p&gt;
&lt;p&gt;Ma règle aujourd'hui :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EnvoyPatchPolicy&lt;/code&gt; seulement en dernier recours, avec un commentaire qui explique pourquoi aucune policy typée ne convient&lt;/li&gt;
&lt;li&gt;le moins de &lt;code&gt;replace&lt;/code&gt; possible, des &lt;code&gt;add&lt;/code&gt; ciblés&lt;/li&gt;
&lt;li&gt;vérifier la configuration réelle avec &lt;code&gt;egctl config envoy-proxy&lt;/code&gt; après chaque montée de version d'Envoy Gateway&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Techniquement, remplacer ingress-nginx par Envoy Gateway n'a rien de compliqué. Les manifests sont plus verbeux, mais plus lisibles et validés par le schéma.&lt;/p&gt;
&lt;p&gt;Le vrai travail, c'est de faire l'inventaire de tout ce que nginx faisait implicitement : bufferiser sur disque, laisser passer n'importe quel upgrade, compter les timeouts entre deux lectures plutôt que sur la durée totale. Aucun de ces comportements n'apparaissait dans nos annotations, et pourtant nos applications en dépendaient.&lt;/p&gt;
&lt;p&gt;Si vous préparez la même migration, mon conseil : faites tourner les deux en parallèle, et testez en priorité les gros uploads, les téléchargements longs, les websockets et l'authentification avec vos comptes aux jetons les plus lourds.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>From ingress-nginx to Envoy Gateway</title>
    <id>https://martin.catty.fr/en/blog/from-ingress-nginx-to-envoy-gateway/</id>
    <link href="https://martin.catty.fr/en/blog/from-ingress-nginx-to-envoy-gateway/" rel="alternate"/>
    <updated>2026-09-30T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Three years ago, I explained how to &lt;a href="https://martin.catty.fr/blog/authentifiez-les-services-derriere-vos-ingress-avec-oauth2-proxy/"&gt;protect services behind your ingress with OAuth2 Proxy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Three annotations on an nginx ingress, a proxy installed once in the cluster, and you were done.&lt;/p&gt;
&lt;p&gt;Today, we removed ingress-nginx from every one of our clusters. And oauth2-proxy with it.&lt;/p&gt;
&lt;p&gt;On paper, migrating off an ingress controller is "just" rewriting manifests. In practice, it mostly means discovering everything nginx was doing for us without being asked.&lt;/p&gt;
&lt;p&gt;This article tells that story, and above all what broke.&lt;/p&gt;
&lt;h3&gt;Why migrate?&lt;/h3&gt;
&lt;p&gt;The most obvious reason: ingress-nginx isn't maintained anymore. The project moved to minimal maintenance and then stopped in March 2026. Keeping an internet-facing component without security fixes isn't an option.&lt;/p&gt;
&lt;p&gt;But it's also a chance to fix an older problem.&lt;/p&gt;
&lt;p&gt;Kubernetes' &lt;code&gt;Ingress&lt;/code&gt; object is quite poor. It can route a host and a path to a service, and that's about it. Everything else (request size limits, timeouts, authentication, websockets, sticky sessions…) goes through annotations specific to each controller:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;nginx.ingress.kubernetes.io/proxy-body-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;nginx.ingress.kubernetes.io/proxy-read-timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3600"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;nginx.ingress.kubernetes.io/affinity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cookie&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;nginx.ingress.kubernetes.io/auth-url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://url-du-proxy/oauth2/auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These annotations became a de facto API: untyped, unvalidated strings that mean nothing to any other controller.&lt;/p&gt;
&lt;p&gt;Gateway API fixes this with a model split across several objects, each owned by a different role:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GatewayClass&lt;/code&gt;: which implementation (here, Envoy Gateway), owned by the platform team&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Gateway&lt;/code&gt;: an entry point, its listeners and ports&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPRoute&lt;/code&gt;: routing for an application, owned by the team that deploys it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And for anything the standard doesn't cover yet, Envoy Gateway offers typed policies that attach to a &lt;code&gt;Gateway&lt;/code&gt; or an &lt;code&gt;HTTPRoute&lt;/code&gt;. We'll run into several of them.&lt;/p&gt;
&lt;h3&gt;Why Envoy Gateway and not Cilium?&lt;/h3&gt;
&lt;p&gt;Our clusters already run Cilium as the CNI, and Cilium implements the Gateway API. The choice seemed obvious.&lt;/p&gt;
&lt;p&gt;It wasn't, for a very concrete reason: sticky sessions.&lt;/p&gt;
&lt;p&gt;One of our applications is stateful on the server side. A user must always land on the same pod, otherwise they lose their session. With nginx, that was the &lt;code&gt;affinity: cookie&lt;/code&gt; annotation.&lt;/p&gt;
&lt;p&gt;In Gateway API, this feature exists (&lt;code&gt;sessionPersistence&lt;/code&gt;, &lt;a href="https://gateway-api.sigs.k8s.io/geps/gep-1619/"&gt;GEP-1619&lt;/a&gt;), but only in the &lt;code&gt;experimental&lt;/code&gt; CRD channel. At the time we made the choice, Cilium didn't implement it, unlike Envoy Gateway.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my-app&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;parentRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;edge&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;hostnames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my-app.example.com&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backendRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my-app&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;8080&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sessionPersistence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;sessionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my_app_sticky&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Cookie&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;absoluteTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;48h&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;cookieConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;lifetimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Permanent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So you need to install the &lt;code&gt;experimental&lt;/code&gt; channel CRDs, and accept that these fields may still evolve. That's a trade-off we made knowingly.&lt;/p&gt;
&lt;h3&gt;Installing without shooting ourselves in the foot&lt;/h3&gt;
&lt;p&gt;Everything is deployed as GitOps with Argo CD. We split the install into three separate applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the CRDs (Gateway API and Envoy Gateway)&lt;/li&gt;
&lt;li&gt;the Envoy Gateway controller&lt;/li&gt;
&lt;li&gt;the cluster-specific objects: &lt;code&gt;GatewayClass&lt;/code&gt;, &lt;code&gt;Gateway&lt;/code&gt;, &lt;code&gt;EnvoyProxy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reason is simple: if the controller and the objects that depend on the CRDs land in the same sync, Argo CD can try to create a &lt;code&gt;Gateway&lt;/code&gt; before its type exists. Splitting the applications (and adding sync waves) guarantees the order.&lt;/p&gt;
&lt;p&gt;This split has another benefit, more important to me: it follows Gateway API's role model. Where a single &lt;code&gt;Ingress&lt;/code&gt; resource mixed everything together (controller choice, TLS, routing, and nginx-specific annotations), Gateway API separates concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GatewayClass&lt;/code&gt; and &lt;code&gt;EnvoyProxy&lt;/code&gt;: how traffic enters the cluster, the platform's job&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Gateway&lt;/code&gt;: entry points, listeners, TLS, still on the platform side&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTPRoute&lt;/code&gt; and the policies attached to a route: an application's routing, owned by the team deploying it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Within a platform mindset, that's exactly what we were after. The infra team owns the three Argo CD applications above. Product teams keep control of the upper layers: their &lt;code&gt;HTTPRoute&lt;/code&gt;, their timeouts, their authentication, versioned alongside their application. They don't need to touch the &lt;code&gt;Gateway&lt;/code&gt; to publish a new path, and a mistake in one route doesn't affect others.&lt;/p&gt;
&lt;p&gt;The boundary is set on the &lt;code&gt;Gateway&lt;/code&gt; itself, with &lt;code&gt;allowedRoutes&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;allowedRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;namespaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;All&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;from: All&lt;/code&gt; lets any namespace attach. To be stricter, &lt;code&gt;from: Selector&lt;/code&gt; restricts attachment to namespaces carrying a label chosen by the platform.&lt;/p&gt;
&lt;h3&gt;Exposing Envoy: a NodePort behind Traefik&lt;/h3&gt;
&lt;p&gt;Our clusters already had an entry point: a load balancer, then Traefik instances on VMs, outside the clusters, terminating TLS and spreading traffic across nodes.&lt;/p&gt;
&lt;p&gt;We kept it as is. Envoy is simply exposed as &lt;code&gt;NodePort&lt;/code&gt;: the same port is opened on every node, and Traefik treats all nodes as backends.&lt;/p&gt;
&lt;p&gt;This is the standard model for Kubernetes outside the cloud. It's actually what cloud load balancers do too: an AWS NLB in instance mode does nothing more than target a NodePort on each node.&lt;/p&gt;
&lt;p&gt;With Envoy Gateway, the service is driven by an &lt;code&gt;EnvoyProxy&lt;/code&gt; object, referenced by the &lt;code&gt;GatewayClass&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EnvoyProxy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway-nodeport&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;edge&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Kubernetes&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;kubernetes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;envoyService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;StrategicMerge&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;NodePort&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;externalTrafficPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Cluster&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10080&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;nodePort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;30080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;externalTrafficPolicy: Cluster&lt;/code&gt; is a deliberate choice. With this value, any node accepts the connection and forwards it to an Envoy pod, possibly on another node. You pay one extra network hop, but every node is a valid backend for Traefik, even if it hosts no Envoy pod. A simple health check is enough.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;Local&lt;/code&gt;, you'd save that hop, but a node without an Envoy pod would refuse traffic. Traefik would then need to know, at all times, which nodes host an Envoy pod.&lt;/p&gt;
&lt;h3&gt;Recovering the client IP&lt;/h3&gt;
&lt;p&gt;Two proxies in a row have a direct consequence: Envoy no longer sees the client's IP, only Traefik's. To recover the real IP, you need to tell it to trust one hop in &lt;code&gt;X-Forwarded-For&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ClientTrafficPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway-trusted-proxy-headers&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;edge&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;targetRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Gateway&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;clientIPDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;xForwardedFor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;numTrustedHops&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Without this, all your access logs, rate limits and IP allowlists see the same address. And with a value set too high, any client can forge its IP by adding an &lt;code&gt;X-Forwarded-For&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;But this setting relies on a strong assumption: only Traefik talks to Envoy. If a client can reach Envoy directly, it writes the last &lt;code&gt;X-Forwarded-For&lt;/code&gt;, and Envoy will take its word for it.&lt;/p&gt;
&lt;h3&gt;Letting only Traefik in&lt;/h3&gt;
&lt;p&gt;A NodePort listens on every interface of every node. Without filtering, any machine on the network can reach Envoy on port 30080, bypassing everything Traefik does (TLS, rate limiting, IP allowlists), and choosing whatever IP it wants to claim.&lt;/p&gt;
&lt;p&gt;The first instinct would be a &lt;code&gt;NetworkPolicy&lt;/code&gt; on the Envoy pods, accepting only Traefik's IPs. With &lt;code&gt;externalTrafficPolicy: Cluster&lt;/code&gt;, that doesn't work: when a node forwards the connection to an Envoy pod on another node, it performs SNAT. The pod only sees the node's IP, not Traefik's.&lt;/p&gt;
&lt;p&gt;So filtering has to happen before load balancing, on the host itself. Cilium can do this with its host firewall (&lt;code&gt;hostFirewall.enabled=true&lt;/code&gt;) and a policy that selects nodes instead of pods:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cilium.io/v2&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;CiliumClusterwideNetworkPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;host-nodeport-envoy&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;nodeSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fromCIDR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10.0.0.15/32&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10.0.1.15/32&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;toPorts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"30080"&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;TCP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Watch out for the trap: as soon as a host policy selects a node, that node switches to default-deny for all incoming traffic. SSH, the API server, kubelet, etcd, the VXLAN tunnel, Cilium's own health checks: anything not explicitly allowed gets cut off.&lt;/p&gt;
&lt;p&gt;So we wrote the full policy (intra-cluster traffic via &lt;code&gt;fromEntities: [cluster, remote-node, health]&lt;/code&gt;, then the admin ports), and first deployed it with &lt;code&gt;policyAuditMode&lt;/code&gt; enabled.&lt;/p&gt;
&lt;p&gt;In that mode, Cilium lets everything through but logs what it would have blocked. A few days of &lt;a href="https://docs.cilium.io/en/stable/observability/hubble/"&gt;Hubble&lt;/a&gt; later, nothing unexpected showed up, and we switched to real enforcement.&lt;/p&gt;
&lt;p&gt;Along the way, this also fixes the &lt;code&gt;X-Forwarded-For&lt;/code&gt; weakness seen above: only Traefik can now reach Envoy, so the last hop is always a trusted proxy.&lt;/p&gt;
&lt;h3&gt;Switching without a big bang&lt;/h3&gt;
&lt;p&gt;For each cluster, we ran nginx and Envoy side by side. Applications were first exposed through Envoy on a different URL, tested, then we switched the real hostname over and removed the ingress.&lt;/p&gt;
&lt;p&gt;It takes longer, but it's exactly what let us discover the following issues during QA, before they reached users.&lt;/p&gt;
&lt;h3&gt;Uploads&lt;/h3&gt;
&lt;p&gt;This is the first thing we hit during QA: on the test URL served by Envoy, uploading large files failed, while it went through fine via nginx.&lt;/p&gt;
&lt;p&gt;On nginx's side, we had &lt;code&gt;proxy-body-size: 0&lt;/code&gt;, meaning no limit. And nginx has a particularity: when a request body exceeds its memory buffer, it writes it to a temporary file on disk. So it can absorb a multi-gigabyte file without blowing up memory.&lt;/p&gt;
&lt;p&gt;Envoy has a different philosophy. It's built to stream: bytes flow through as they arrive, and it applies a per-connection buffer limit (32 KB by default) for flow control. As long as nothing needs the full body, it works great.&lt;/p&gt;
&lt;p&gt;Concretely, when the backend reads slower than the client sends, Envoy buffers up to that limit, then stops reading the client's socket until it drains. The client slows down, memory stays stable. A 5 GB file passes through Envoy without ever occupying more than a few tens of kilobytes.&lt;/p&gt;
&lt;p&gt;The problem shows up as soon as something in the chain needs the full body before letting it through. At that point, the limit stops being a flow-control threshold and becomes a hard cap. Beyond it, the request gets rejected with a &lt;code&gt;413 Payload Too Large&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We had to find out what, in our chain, was requiring the full body.&lt;/p&gt;
&lt;h4&gt;The culprit: our WAF&lt;/h4&gt;
&lt;p&gt;We had used the migration as an opportunity to add a WAF, &lt;a href="https://coraza.io/"&gt;Coraza&lt;/a&gt;, loaded into Envoy as a Wasm module, with the OWASP CRS rules. It was in &lt;code&gt;DetectionOnly&lt;/code&gt; mode: it logs what it would have blocked, but blocks nothing. Or so we thought.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;EnvoyExtensionPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;coraza-waf&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;edge&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;targetRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Gateway&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;envoy-gateway&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;wasm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;coraza-waf&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;rootID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;waf&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;failOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Image&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ghcr.io/corazawaf/coraza-proxy-wasm:0.6.0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;default_directives&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;default&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;directives_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Include @recommended-conf&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Include @crs-setup-conf&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Include @owasp_crs/*.conf&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecRuleEngine DetectionOnly&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecRequestBodyAccess On&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To inspect a request body (looking for a SQL injection in a form, for example), a WAF needs it in full. That's what &lt;code&gt;SecRequestBodyAccess On&lt;/code&gt; does: Coraza asks Envoy to buffer the body before passing it on to the backend. That's exactly the case that turns the buffer limit into a hard cap.&lt;/p&gt;
&lt;p&gt;And there's a second layer. Coraza's recommended configuration sets &lt;code&gt;SecRequestBodyInMemoryLimit&lt;/code&gt; to 128 KiB, with &lt;code&gt;SecRequestBodyLimitAction Reject&lt;/code&gt;. Beyond 128 KiB, the request is rejected.&lt;/p&gt;
&lt;p&gt;The killer detail: this rejection doesn't go through the rule engine. So it applies even in &lt;code&gt;DetectionOnly&lt;/code&gt;. Our "observation only" WAF was blocking every upload over 128 KiB.&lt;/p&gt;
&lt;h4&gt;The wrong answer&lt;/h4&gt;
&lt;p&gt;The instinctive move would be to raise Envoy's buffer limit, in the &lt;code&gt;ClientTrafficPolicy&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;bufferLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It works, partially: Envoy accepts keeping large bodies in memory. But it's a very bad idea.&lt;/p&gt;
&lt;p&gt;This limit applies per connection, across every route in the cluster. A few concurrent large uploads, or a fast client facing a slow backend, and an Envoy pod can consume several gigabytes before getting OOM-killed. And all of the cluster's traffic flows through these pods. For comparison, Envoy's own guidance for an edge-facing proxy recommends staying around 32 KB.&lt;/p&gt;
&lt;p&gt;And why not do like nginx and write to disk? Envoy does have a filter for that (&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/file_system_buffer_filter"&gt;&lt;code&gt;file_system_buffer&lt;/code&gt;&lt;/a&gt;), but &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/file_system_buffer/v3/file_system_buffer.proto"&gt;its documentation&lt;/a&gt; explicitly says: work in progress, not meant for production. Envoy Gateway doesn't even expose it.&lt;/p&gt;
&lt;h4&gt;The right answer&lt;/h4&gt;
&lt;p&gt;The problem wasn't the buffer size, it was that something demanded the full body. The fix is to tell Coraza to inspect the start of the body and let the rest stream through:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecRequestBodyAccess On&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecRequestBodyLimitAction ProcessPartial&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With &lt;code&gt;ProcessPartial&lt;/code&gt;, Coraza analyzes the first 128 KiB and lets the rest flow without holding it back. Regular requests (forms, API JSON) still get inspected in full, and uploads go back to streaming.&lt;/p&gt;
&lt;p&gt;This trade-off has a cost, and it's worth naming: an attacker can fill the first 128 KiB with harmless content, then place their payload after it. Coraza inspects the start, sees nothing, and lets the rest through without ever reading it. This is a well-known WAF bypass.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;DetectionOnly&lt;/code&gt;, it's acceptable: the WAF blocks nothing anyway, it's there for observation. But it's not the configuration to keep once you switch to blocking mode.&lt;/p&gt;
&lt;p&gt;The goal is to not treat every route the same way. SQL, XSS or command injections go through form fields or JSON, which are a few kilobytes at most. A body of several hundred kilobytes on a route that isn't expecting a file is already suspicious on its own. Hence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;strict inspection by default (&lt;code&gt;Reject&lt;/code&gt;), via an &lt;code&gt;EnvoyExtensionPolicy&lt;/code&gt; attached to the &lt;code&gt;Gateway&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a dedicated &lt;code&gt;EnvoyExtensionPolicy&lt;/code&gt;, attached only to upload &lt;code&gt;HTTPRoute&lt;/code&gt;s, using &lt;code&gt;ProcessPartial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;on those upload routes, protection carried by the application itself: file type, maximum size, antivirus scanning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Envoy's buffer limit went back down to a sane value, and Envoy pod memory no longer depends on uploaded file sizes.&lt;/p&gt;
&lt;p&gt;The lesson goes beyond the WAF. With Envoy, when a large request body causes trouble, the right question isn't "which limit should I raise?" but "which filter needs the full body, and does it really need it?".&lt;/p&gt;
&lt;p&gt;An ext_authz filter forwarding the body to an authorization service, a Lua filter, or another Wasm module can all produce the exact same effect.&lt;/p&gt;
&lt;h3&gt;Timeouts that don't mean the same thing&lt;/h3&gt;
&lt;p&gt;Second surprise, on long downloads and server-side processing that takes several minutes.&lt;/p&gt;
&lt;p&gt;With nginx, &lt;code&gt;proxy_read_timeout&lt;/code&gt; is a delay between two reads: as long as the backend keeps sending bytes regularly, the connection stays alive.&lt;/p&gt;
&lt;p&gt;With Envoy, the route timeout is a total duration: the time given to the backend to send the complete response, starting from when the request was fully received. And its default value is 15 seconds.&lt;/p&gt;
&lt;p&gt;An export that takes two minutes to generate? Cut off at 15 seconds, with a &lt;code&gt;504&lt;/code&gt; and an &lt;code&gt;upstream request timeout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Gateway API lets you configure this per route rule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backendRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my-app&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;8080&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;timeouts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5m&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;backendRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Rather than raising the value globally, we did it route by route, only where the need was real. A long timeout everywhere also means connections piling up when a backend is slow.&lt;/p&gt;
&lt;h3&gt;Websockets and custom protocols&lt;/h3&gt;
&lt;p&gt;With nginx, websockets worked with two timeout annotations. With Envoy, a connection upgrade is only accepted if it's explicitly allowed.&lt;/p&gt;
&lt;p&gt;For an HTML5 remote desktop application that keeps a websocket connection open for the whole session:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;BackendTrafficPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;remote-desktop-session&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;targetRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;remote-desktop&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;httpUpgrade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;websocket&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;requestTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0s&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;connectionIdleTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3600s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;requestTimeout: 0s&lt;/code&gt; disables the total timeout (a session can last hours), and &lt;code&gt;connectionIdleTimeout&lt;/code&gt; cuts off genuinely idle sessions.&lt;/p&gt;
&lt;p&gt;The most interesting case was Headscale, the open source implementation of the Tailscale control server. The Tailscale client doesn't perform a websocket upgrade: it requests an upgrade to its own protocol, &lt;code&gt;tailscale-control-protocol&lt;/code&gt;, to establish an encrypted channel using the Noise protocol.&lt;/p&gt;
&lt;p&gt;Nginx let through any &lt;code&gt;Upgrade&lt;/code&gt; header. Envoy refuses it until it's been declared:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;httpUpgrade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;websocket&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tailscale-control-protocol&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The symptom itself wasn't obvious at all: clients unable to register, and nothing informative on the application side. This is exactly the kind of implicit behavior you don't suspect until you switch proxies.&lt;/p&gt;
&lt;h3&gt;Goodbye oauth2-proxy&lt;/h3&gt;
&lt;p&gt;This is the part I enjoyed the most.&lt;/p&gt;
&lt;p&gt;Envoy handles OIDC natively. Authentication is declared in a &lt;code&gt;SecurityPolicy&lt;/code&gt; attached to a route:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.envoyproxy.io/v1alpha1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;SecurityPolicy&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my-app-oidc&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;targetRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gateway.networking.k8s.io&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my-app&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"https://login.microsoftonline.com/${tenant_id}/v2.0"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"${client_id}"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my-app-oidc&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;openid&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;email&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;profile&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;cookieConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Lax&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Compared to the approach in my 2023 article, several things change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no more third-party service to deploy, monitor and keep updated&lt;/li&gt;
&lt;li&gt;authentication is declared per route, instead of a chain of annotations pointing to another service&lt;/li&gt;
&lt;li&gt;each application has its own OIDC client, so its own permissions. With oauth2-proxy, the cookie's scope (&lt;code&gt;*.staging.example.com&lt;/code&gt; in the old example) granted access to every service on the domain at once&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is a trap though, and not a small one.&lt;/p&gt;
&lt;p&gt;By default, Envoy encrypts the tokens it stores in cookies. But browsers cap a cookie at roughly 4096 bytes (name and value combined).&lt;/p&gt;
&lt;p&gt;For some users, whose access token carries a lot of claims (group memberships, for instance), the encrypted cookie exceeds that size. The browser silently drops it, and the user loops back to the login screen forever.&lt;/p&gt;
&lt;p&gt;Only certain accounts were affected, which made the diagnosis fairly painful.&lt;/p&gt;
&lt;p&gt;This is a structural limitation of Envoy Gateway's OIDC implementation: it only knows cookies as a session mechanism. oauth2-proxy, on the other hand, supports several storage backends, including Redis: the cookie then only holds a session identifier, and the full token stays server-side. Token size becomes a non-issue, at the cost of running a stateful service, the very thing we were trying to remove.&lt;/p&gt;
&lt;p&gt;For internal services, we disabled token encryption (cookies stay &lt;code&gt;HttpOnly&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;disableTokenEncryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's a trade-off. On a publicly exposed service, I'd rather reduce the token size on the identity provider side (fewer claims) than disable encryption.&lt;/p&gt;
&lt;h3&gt;EnvoyPatchPolicy: an escape hatch to use carefully&lt;/h3&gt;
&lt;p&gt;When a policy doesn't cover a need, Envoy Gateway offers an escape hatch: &lt;code&gt;EnvoyPatchPolicy&lt;/code&gt;. It lets you apply a JSON Patch directly to the xDS configuration generated for Envoy.&lt;/p&gt;
&lt;p&gt;It's powerful, and that's exactly the problem.&lt;/p&gt;
&lt;p&gt;xDS resource names are generated by the controller, and can change between versions. A hand-written patch relies on a structure nobody guarantees. And crucially, it applies after every other policy.&lt;/p&gt;
&lt;p&gt;We learned this the hard way: a &lt;code&gt;replace&lt;/code&gt;-type patch on a listener's routes overwrote the configuration generated by an OIDC &lt;code&gt;SecurityPolicy&lt;/code&gt;. Nothing in the Kubernetes objects showed it: the &lt;code&gt;SecurityPolicy&lt;/code&gt; was accepted, its status was correct, but the configuration actually sent to Envoy no longer contained it.&lt;/p&gt;
&lt;p&gt;My rule today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EnvoyPatchPolicy&lt;/code&gt; only as a last resort, with a comment explaining why no typed policy fits&lt;/li&gt;
&lt;li&gt;as few &lt;code&gt;replace&lt;/code&gt; operations as possible, prefer targeted &lt;code&gt;add&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;verify the actual running configuration with &lt;code&gt;egctl config envoy-proxy&lt;/code&gt; after every Envoy Gateway version bump&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Technically, replacing ingress-nginx with Envoy Gateway isn't complicated. The manifests are more verbose, but more readable and schema-validated.&lt;/p&gt;
&lt;p&gt;The real work is taking inventory of everything nginx was doing implicitly: buffering to disk, letting through any upgrade header, counting timeouts between reads rather than over total duration. None of this behavior showed up in our annotations, and yet our applications depended on it.&lt;/p&gt;
&lt;p&gt;If you're planning the same migration, my advice: run both side by side, and prioritize testing large uploads, long downloads, websockets, and authentication with your accounts carrying the heaviest tokens.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Utiliser IRSA dans un cluster Kubernetes hors EKS</title>
    <id>https://martin.catty.fr/blog/utiliser-irsa-dans-un-cluster-kubernetes-hors-eks/</id>
    <link href="https://martin.catty.fr/blog/utiliser-irsa-dans-un-cluster-kubernetes-hors-eks/" rel="alternate"/>
    <updated>2025-06-22T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;AWS propose un système à la fois puissant et sécurisé pour donner des autorisations à nos pods Kubernetes sans avoir à manipuler (et assurer la rotation) de clés ou token, il s’agit d’IRSA.&lt;/p&gt;
&lt;h2&gt;À quoi ça sert ?&lt;/h2&gt;
&lt;p&gt;Imaginez que votre application ait besoin d’accéder à un bucket S3. Vous allez probablement créer un rôle qui aura les droits suffisants pour faire le nécessaire : lire, écrire…&lt;/p&gt;
&lt;p&gt;Pour utiliser ce rôle vous pouvez créer des identifiants (key id et key secret) et les injecter dans votre pod sous forme de secrets.&lt;/p&gt;
&lt;p&gt;Mais AWS propose un mécanisme plus élégant et sécurisé via &lt;code&gt;IRSA&lt;/code&gt; (IAM role for Service Account).&lt;/p&gt;
&lt;h2&gt;Service account&lt;/h2&gt;
&lt;p&gt;Lorsque nous crééons un pod dans notre cluster Kubernetes, celui ci hérite (ou pas selon notre configuration) d’un token, celui du &lt;code&gt;ServiceAccount&lt;/code&gt; configuré par notre pod.&lt;/p&gt;
&lt;p&gt;Ce &lt;code&gt;ServiceAccount&lt;/code&gt; donne une identité à notre pod et permet d’y associer des permissions de type &lt;code&gt;RBAC&lt;/code&gt; (Role Based Access Control).&lt;/p&gt;
&lt;p&gt;L’authentification du pod se fait au travers d’un jeton JWT, qui est stocké par défaut dans &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Si on décode un jeton, par exemple celui d’un pod qui fait tourner ce site :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jq&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'split(".")|{header: .[0]|@base64d|fromjson, payload: .[1]|@base64d|fromjson}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;on voit bien le contenu de notre JWT :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Z_ktNvbbBECOxLclHC46p5cMlFTGxzQVtfD50S41Ld8"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"https://kubernetes.default.svc.cluster.local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"k3s"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1781121747&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1749585747&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://kubernetes.default.svc.cluster.local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"jti"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f27e4a7b-b1c7-43a6-b327-6022edfb82fc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"kubernetes.io"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"namespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"website"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker-3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f3266943-2933-487d-9a35-e6a756a4b05e"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"pod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"website-b49bb9b7f-b45pc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e399270b-526c-45b8-ac0a-141c85e7f2fe"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"serviceaccount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6c44f7d8-f28f-4ff0-a18d-eb1a6edb0966"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"warnafter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1749589354&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"nbf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1749585747&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"system:serviceaccount:website:default"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notre pod utilise le &lt;code&gt;ServiceAccount&lt;/code&gt; &lt;code&gt;default&lt;/code&gt;, dans le namespace &lt;code&gt;website&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Si on fait la même chose pour un pod lambda qui tourne dans un cluster EKS:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a715867f84eace896d9a43b605e29ba93b3d9f09"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"https://kubernetes.default.svc"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1782141601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1750605601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://oidc.eks.ca-central-1.amazonaws.com/id/76DA39F82A5363AA579A40F5296B8E2C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"jti"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bae90f82-c1d0-489b-80a8-4460b2e39053"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"kubernetes.io"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"namespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incredible"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ip-10-0-3-214.ca-central-1.compute.internal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cf60907d-369c-42fd-9e70-f642465de26a"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"pod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incredible-client-6b647595dd-wxc6b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a60b5efb-1d60-4e40-b350-664906952ddf"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"serviceaccount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"incredible-client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6afe8c21-70bc-4787-8b2c-2a04750a4996"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"warnafter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1750609208&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"nbf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1750605601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"system:serviceaccount:incredible:incredible-client"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On a globalement la même chose, hormis pour la partie &lt;code&gt;iss&lt;/code&gt; (issuer). En effet notre cluster dispose par défaut d’une URL OpenID Connect.&lt;/p&gt;
&lt;p&gt;C’est l’URL de notre fournisseur d’identité (idp), propre à chaque cluster EKS. Nous ne pouvons pas directement voir comment cela fonctionne car c’est géré par le control plane de Kubernetes auquel nous n’avons pas accès.&lt;/p&gt;
&lt;p&gt;Mais c’est sa responsabilité de fournir un token JWT valide.&lt;/p&gt;
&lt;p&gt;Pour chacun des appels du pod à l’API server ce token va être repassé en bearer et validé par l’api server (signature, subject) afin de déterminer si le &lt;code&gt;ServiceAccount&lt;/code&gt; a les permissions suivantes pour faire ce qu’il demande.&lt;/p&gt;
&lt;h2&gt;Attacher un rôle à un service account avec AWS&lt;/h2&gt;
&lt;p&gt;Imaginons que nous ayons créé un rôle &lt;code&gt;s3-role&lt;/code&gt; dans AWS permettant d’accéder à notre bucket.&lt;/p&gt;
&lt;p&gt;Nous pouvons, dans un contexte EKS, créer un &lt;code&gt;ServiceAccount&lt;/code&gt; en l’annotant du rôle qu’il pourra endosser.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;v1&lt;/span&gt;
&lt;span class="nt"&gt;automountServiceAccountToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;eks.amazonaws.com/role-arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;arn:aws:iam::AWS_ACCOUNT_ID:role/s3-role&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si un pod utilise ce &lt;code&gt;ServiceAccount&lt;/code&gt;, il pourra automatiquement assumer le rôle IAM associé et accéder à notre bucket sans fournir explicitement de clés d’accès ou secret.&lt;/p&gt;
&lt;p&gt;Derrière le rideau, EKS fournit au pod un token JWT signé, qu’il peut présenter à AWS STS pour obtenir des identifiants temporaires (access key, secret key, session token).&lt;/p&gt;
&lt;h2&gt;Attacher un rôle à un service account d’un autre compte AWS&lt;/h2&gt;
&lt;p&gt;La force du système réside dans le fait qu’il s’appuie sur des standards ouverts, notamment OpenID Connect (OIDC).&lt;/p&gt;
&lt;p&gt;AWS permet de fédérer plusieurs fournisseurs d’identité (IdP), y compris de clusters EKS issus d’autres comptes AWS, via l’ajout de leurs URL OIDC.&lt;/p&gt;
&lt;p&gt;Cette URL devient alors une source de confiance : AWS est capable de vérifier les tokens JWT émis par ce cluster et de permettre l’accès à ses services via des rôles IAM conditionnés au sub (subject) ou à d’autres claims du token.&lt;/p&gt;
&lt;h2&gt;Fédérer un cluster non AWS dans AWS&lt;/h2&gt;
&lt;p&gt;Après cette longue introduction, venant en aux faits. À partir du moment ou AWS s’appuie sur des standards je me suis mis en tête de fédérer un cluster non EKS dans mon tenant AWS.&lt;/p&gt;
&lt;p&gt;Le but est de bénéficier du même mécanisme IRSA dans des clusters externes, ici avec un cluster k3s.&lt;/p&gt;
&lt;p&gt;Dans mon cas cela me sert à « redescendre » des secrets gérés dans AWS secrets manager avec &lt;a href="https://external-secrets.io/"&gt;external secrets operator&lt;/a&gt;. Mais nous aurons l’occasion d’en reparler dans un autre article 😊.&lt;/p&gt;
&lt;h3&gt;Exposer la configuration&lt;/h3&gt;
&lt;p&gt;Pour fonctionner, OpenID Connect s'appuie sur un standard qui nécessite l'exposition d'informations de configuration sur un endpoint bien défini.&lt;/p&gt;
&lt;p&gt;Cette approche permet à AWS (ou tout autre service) de découvrir automatiquement comment interagir avec notre fournisseur d'identité.&lt;/p&gt;
&lt;h3&gt;Le point de découverte OpenID Connect&lt;/h3&gt;
&lt;p&gt;Le standard OpenID Connect définit un endpoint de découverte situé à l'adresse :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://url/.well-known/openid-configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cette URL va exposer un fichier JSON de configuration qui décrit les capacités de notre fournisseur d'identité. Par exemple :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://url-de-mon-cluster.tld/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"jwks_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://url-de-mon-cluster.tld/keys.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"authorization_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:kubernetes:programmatic_authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"response_types_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"id_token"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"subject_types_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"id_token_signing_alg_values_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"claims_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"iss"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;issuer&lt;/code&gt; : L'URL qui identifie de manière unique notre fournisseur d'identité. C'est cette valeur qui sera utilisée dans le champ &lt;code&gt;iss&lt;/code&gt; des tokens JWT émis par notre cluster.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jwks_uri&lt;/code&gt; : L'URL où AWS pourra récupérer les clés publiques utilisées pour vérifier la signature des tokens JWT.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;authorization_endpoint&lt;/code&gt; : Ce champ est requis par la spécification OIDC, mais Kubernetes ne propose pas de login interactif. Nous utilisons donc une valeur symbolique (&lt;code&gt;urn:kubernetes:programmatic_authorization&lt;/code&gt;) pour indiquer que l’authentification est entièrement non interactive (par jetons JWT générés automatiquement pour les pods).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;id_token_signing_alg_values_supported&lt;/code&gt; : Les algorithmes de signature supportés (RS256 est l'algorithme standard recommandé).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;claims_supported&lt;/code&gt; : Les claims (revendications) que notre fournisseur d'identité peut inclure dans les tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AWS a besoin de ces informations pour :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vérifier que les tokens proviennent bien de notre cluster&lt;/li&gt;
&lt;li&gt;Vérifier les signatures : utiliser les clés publiques pour valider la signature des tokens JWT&lt;/li&gt;
&lt;li&gt;Savoir quelles informations sont disponibles dans les tokens pour les utiliser dans les politiques IAM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sans cette exposition, AWS ne pourrait pas faire confiance aux tokens émis par notre cluster k3s.&lt;/p&gt;
&lt;p&gt;Dans mon cas j’ai simplement exposé ce endpoint de manière statique via mon ingress. Les JSON &lt;code&gt;openid-configuration&lt;/code&gt; et &lt;code&gt;keys.json&lt;/code&gt; sont stockés dans des configmap.&lt;/p&gt;
&lt;h3&gt;Générer des clés de signature pour les ServiceAccount&lt;/h3&gt;
&lt;p&gt;Pour que notre cluster k3s puisse émettre des tokens JWT valides et sécurisés, nous devons configurer des clés de signature permettent de signer ces tokens.&lt;/p&gt;
&lt;h4&gt;Pourquoi des clés de signature ?&lt;/h4&gt;
&lt;p&gt;Les tokens JWT contiennent des informations sensibles (namespace, service account, etc.).&lt;/p&gt;
&lt;p&gt;Sans signature cryptographique, n'importe qui pourrait créer des tokens frauduleux.&lt;/p&gt;
&lt;p&gt;Les clés de signature permettent à AWS de vérifier que les tokens proviennent bien de notre cluster.&lt;/p&gt;
&lt;h4&gt;Génération de la paire de clés RSA&lt;/h4&gt;
&lt;p&gt;Nous utilisons &lt;code&gt;RSA-2048&lt;/code&gt;, un algorithme de cryptographie asymétrique standard et sécurisé :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;rsa&lt;span class="w"&gt; &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;signing.key&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;pemls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cette commande génère :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;signing.key&lt;/code&gt; : La clé privée (à garder secrète)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;signing.key.pub&lt;/code&gt; : La clé publique (que nous exposerons plus tard via l'endpoint JWKS)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;signing.key*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Conversion au format PKCS8&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;PKCS8&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;signing.key.pub&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;signing.key.pub.pkcs8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cette conversion transforme la clé publique au format PKCS8, qui est le standard attendu par les systèmes OpenID Connect et AWS pour la validation des signatures JWT.&lt;/p&gt;
&lt;h4&gt;Utilisation dans k3s&lt;/h4&gt;
&lt;p&gt;Une fois ces clés générées, elles doivent être configurées dans notre service k3s pour signer les tokens des &lt;code&gt;ServiceAccount&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La clé privée reste dans le cluster pour signer les tokens, tandis que la clé publique est exposée via l'endpoint JWKS pour permettre la vérification par AWS.&lt;/p&gt;
&lt;p&gt;On dépose nos fichiers précédemment générés (&lt;code&gt;signing.key&lt;/code&gt;, &lt;code&gt;signing.key.pub.pkcs8&lt;/code&gt;) dans le dossier &lt;code&gt;/etc/rancher/k3s/tls/&lt;/code&gt; sur les master qui gèrent le control plane de notre cluster.&lt;/p&gt;
&lt;p&gt;Nous allons maintenant reconfigurer le service k3s pour utiliser ces clés. Pour cela on met à jour le fichier du service &lt;code&gt;/etc/systemd/system/k3s.service&lt;/code&gt;, pour y ajouter les paramètres de démarrage suivant :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin/k3s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;'--kube-apiserver-arg=api-audiences=sts.amazonaws.com'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;'--kube-apiserver-arg=service-account-key-file=/etc/rancher/k3s/tls/signing.key.pub.pkcs8'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;'--kube-apiserver-arg=service-account-key-file=/var/lib/rancher/k3s/server/tls/service.key'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;'--kube-apiserver-arg=service-account-signing-key-file=/etc/rancher/k3s/tls/signing.key'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;'--kube-apiserver-arg=service-account-issuer=https://oidc-url.tld'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;'--kube-apiserver-arg=service-account-issuer=k3s'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les paramètres &lt;code&gt;--kube-apiserver-arg=service-account-key-file&lt;/code&gt; et &lt;code&gt;--kube-apiserver-arg=service-account-issuer&lt;/code&gt; peuvent être passés plusieurs fois, ce qui n’oblige pas à invalider les token précédemment émis.&lt;/p&gt;
&lt;p&gt;Les tokens nouvellement émis auront donc l’audience &lt;code&gt;sts.amazonaws.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Nous pouvons maintenant redémarrer notre k3s.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;k3s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Récupération et exposition des clés&lt;/h3&gt;
&lt;p&gt;Depuis une des machines du cluster k3s :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;--raw&lt;span class="w"&gt; &lt;/span&gt;/openid/v1/jwks&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;keys.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Il reste à récupérer ce fichier et à l’exposer au même titre que &lt;code&gt;.well-known/openid-configuration&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Injecter automatiquement les credentials&lt;/h3&gt;
&lt;p&gt;Comme nous ne sommes pas dans un cluster EKS, nous ne disposons pas automatiquement d’une solution permettant de dynamiquement injecter des credentials dans le pod.&lt;/p&gt;
&lt;p&gt;En effet, comme expliqué plus tôt, AWS le gère automatiquement pour nous en coulisse en s’occupant de fournir et gérer la rotation des tokens dans un cluster EKS.&lt;/p&gt;
&lt;p&gt;Toutefois Amazon met à disposition le code d’&lt;a href="https://github.com/aws/amazon-eks-pod-identity-webhook/tree/master"&gt;&lt;code&gt;Amazon EKS Pod Identity Webhook&lt;/code&gt;&lt;/a&gt; que l’on va pouvoir installer et utiliser dans notre cluster k3s.&lt;/p&gt;
&lt;p&gt;Un pré-requis est d’avoir un cert manager :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;https://github.com/cert-manager/cert-manager/releases/download/v1.18.0/cert-manager.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Et après avoir cloné le dépôt :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make&lt;span class="w"&gt; &lt;/span&gt;cluster-up&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amazon/amazon-eks-pod-identity-webhook:latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous voyons les objets Kubernetes suivants :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;NAME&lt;span class="w"&gt;                                        &lt;/span&gt;READY&lt;span class="w"&gt;   &lt;/span&gt;STATUS&lt;span class="w"&gt;    &lt;/span&gt;RESTARTS&lt;span class="w"&gt;   &lt;/span&gt;AGE
pod/pod-identity-webhook-764c6c7bdc-6chtr&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/1&lt;span class="w"&gt;     &lt;/span&gt;Running&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;11d

NAME&lt;span class="w"&gt;                           &lt;/span&gt;TYPE&lt;span class="w"&gt;        &lt;/span&gt;CLUSTER-IP&lt;span class="w"&gt;      &lt;/span&gt;EXTERNAL-IP&lt;span class="w"&gt;   &lt;/span&gt;PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;AGE
service/kubernetes&lt;span class="w"&gt;             &lt;/span&gt;ClusterIP&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.43.0.1&lt;span class="w"&gt;       &lt;/span&gt;&amp;lt;none&amp;gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;/TCP&lt;span class="w"&gt;   &lt;/span&gt;15d
service/pod-identity-webhook&lt;span class="w"&gt;   &lt;/span&gt;ClusterIP&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.43.135.168&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;none&amp;gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;/TCP&lt;span class="w"&gt;   &lt;/span&gt;11d

NAME&lt;span class="w"&gt;                                   &lt;/span&gt;READY&lt;span class="w"&gt;   &lt;/span&gt;UP-TO-DATE&lt;span class="w"&gt;   &lt;/span&gt;AVAILABLE&lt;span class="w"&gt;   &lt;/span&gt;AGE
deployment.apps/pod-identity-webhook&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/1&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;11d

NAME&lt;span class="w"&gt;                                              &lt;/span&gt;DESIRED&lt;span class="w"&gt;   &lt;/span&gt;CURRENT&lt;span class="w"&gt;   &lt;/span&gt;READY&lt;span class="w"&gt;   &lt;/span&gt;AGE
replicaset.apps/pod-identity-webhook-764c6c7bdc&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;11d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Il ne nous reste qu’à ajouter l’URL de notre IDP dans la console AWS pour qu’elle soit fédérée.&lt;/p&gt;
&lt;h3&gt;Tester IRSA avec le cli AWS&lt;/h3&gt;
&lt;p&gt;Si vous voulez vérifier la capacité de vos tokens à assumer un rôle IAM, vous pouvez récupérer le token JWT d’un de vos pods, le stocker par exemple dans un fichier &lt;code&gt;token.jwt&lt;/code&gt; et jouer une commande de ce type :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;aws&lt;span class="w"&gt; &lt;/span&gt;sts&lt;span class="w"&gt; &lt;/span&gt;assume-role-with-web-identity&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--role-arn&lt;span class="w"&gt; &lt;/span&gt;arn-du-role&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--role-session-name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--web-identity-token&lt;span class="w"&gt; &lt;/span&gt;file://token.jwt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Nous avons vu comment faire bénéficier à un cluster tiers du mécanisme IRSA pour interagir avec des services AWS, ce qui nous offre le meilleur des deux mondes.&lt;/p&gt;
&lt;p&gt;Nous pouvons ainsi bénéficier d’une intégration sécurisée de certains services d’AWS pour nos applications mais sans nécessairement déployer de clusters Kubernetes avec EKS.&lt;/p&gt;
&lt;p&gt;ⓕ ⓤ ⓢ ⓔ 👊🏼&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Authentifiez les services derrière vos ingress avec OAuth2 Proxy</title>
    <id>https://martin.catty.fr/blog/authentifiez-les-services-derriere-vos-ingress-avec-oauth2-proxy/</id>
    <link href="https://martin.catty.fr/blog/authentifiez-les-services-derriere-vos-ingress-avec-oauth2-proxy/" rel="alternate"/>
    <updated>2023-11-07T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Kubernetes nous offre des possibilités d’industrialisation de nos infrastructures. C’est particulièrement intéressant dans le cadre d’environnements de recette ou d’intégration.&lt;/p&gt;
&lt;p&gt;On peut en effet rapidement se retrouver à avoir plusieurs environnements distincts (intégration, revue, recette…) par projet.&lt;/p&gt;
&lt;p&gt;Ces environnements, quand ils veulent être rendus accessibles à des tiers,  peuvent se retrouver exposés sur Internet.&lt;/p&gt;
&lt;p&gt;Dès lors, leur sécurisation est un enjeu qui ne peut pas être ignoré.&lt;/p&gt;
&lt;h3&gt;Anatomie d’une application simple&lt;/h3&gt;
&lt;p&gt;Pour une application classique, on va retrouver les composants Kubernetes suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ingress&lt;/li&gt;
&lt;li&gt;service&lt;/li&gt;
&lt;li&gt;pod&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L’ingress est la porte d’entrée sur l’extérieur, jouant le rôle de reverse proxy vers votre service qui sera lui en charge de répartir les requêtes sur les différentes occurences de votre application (pod).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flux d’une requête HTTP" src="/images/flux_dune_requete_http.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Sécuriser ses ingress avec OAuth2 proxy&lt;/h3&gt;
&lt;h4&gt;Choix de OAuth2 proxy&lt;/h4&gt;
&lt;p&gt;Dans notre contexte nous avons un cluster destiné à nos applications en phase de recette. Chaque projet peut être déployé N fois selon l’environnement ciblé.&lt;/p&gt;
&lt;p&gt;On peut donc progressivement se retrouver avec un nombre conséquent d’ingress dans notre cluster.&lt;/p&gt;
&lt;p&gt;Notre objectif est de sécuriser simplement chacun de ces ingress en le mettant derrière une authentification de type Azure Active Directory.&lt;/p&gt;
&lt;p&gt;En arrivant sur notre service on devra être redirigé vers la mire de login si l’on est pas encore authentifié. Notre autre pré-requis est que la configuration par ingress soit la plus simple possible.&lt;/p&gt;
&lt;p&gt;Pour se faire, notre choix se porte sur OAuth2 Proxy. À l’image d’un cert manager le service s’installe et se configure de façon unique dans le cluster.&lt;/p&gt;
&lt;p&gt;La configuration sur les ingress cibles se fait ensuite avec quelques annotations que nous détaillerons plus loin.&lt;/p&gt;
&lt;h4&gt;Fonctionnement de OAuth2 proxy&lt;/h4&gt;
&lt;p&gt;Imaginons que nous souhaitons exposer notre application sur &lt;code&gt;foo.recette.bar.fr&lt;/code&gt;. L’ingress de l’application répondra donc sur ce domaine. Le proxy sera lui exposé sur &lt;code&gt;proxy.recette.bar.fr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lorsqu’on se rendra sur cette URL, l’ingress interrogera le service OAuth2 proxy en passant automatiquement les cookies accessibles à ce dernier.&lt;/p&gt;
&lt;p&gt;Si ce cookie est valide il ne se passe rien de plus, la requête entrante est considérée comme authentifiée. Si ce n’est pas le cas le proxy enclenche un round trip OAuth 2 classique. L’utilisateur sera redirigé vers la mire de login de son Active Directory.&lt;/p&gt;
&lt;p&gt;Une fois authentifié, il sera renvoyé vers le proxy qui écrira un cookie puis le redirigera vers l’app en question.&lt;/p&gt;
&lt;p&gt;Il est important d’avoir en tête que la portée du cookie détermine la portée de l’authentification.&lt;/p&gt;
&lt;p&gt;Ici mon cookie a pour portée &lt;code&gt;recette.bar.fr&lt;/code&gt;. Cela signifie qu’une fois authentifié je le serai sur l’ensemble des services qui sont sur &lt;code&gt;*.recette.bar.fr&lt;/code&gt; de manière indifférenciée.&lt;/p&gt;
&lt;p&gt;Sauf à démultiplier les proxy ce n’est donc pas une solution permettant de gérer finement des accès par application.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Exemple de cookies déposés par le proxy" src="/images/exemple_de_cookies_deposes_par_le_proxy.webp" /&gt;&lt;/p&gt;
&lt;h4&gt;Mise en place et configuration de OAuth2 proxy&lt;/h4&gt;
&lt;p&gt;L’installation se fait simplement avec Helm :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;helm&lt;span class="w"&gt; &lt;/span&gt;repo&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;oauth2-proxy&lt;span class="w"&gt; &lt;/span&gt;https://oauth2-proxy.github.io/manifests
helm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;oauth2-proxy&lt;span class="w"&gt; &lt;/span&gt;oauth2-proxy/oauth2-proxy&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;mon-namespace
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Une fois réalisée nous allons modifier la configuration du secret créé pour nous :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;edit&lt;span class="w"&gt; &lt;/span&gt;secret&lt;span class="w"&gt; &lt;/span&gt;oauth2-proxy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;3 clés attendent une valeur :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;client-id&lt;/code&gt; : l’identification de l’app registration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client-secret&lt;/code&gt; : le secret de l’app registration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cookie-secret&lt;/code&gt; : une valeur aléatoire, jouant le rôle de salt pour chiffrer les cookies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note : il est également possible d’utiliser redis en backend de session mais votre proxy ne sera plus stateless.&lt;/p&gt;
&lt;p&gt;Je ne détaille pas le process de création d’une app registration dans le portail Azure, vous trouverez facilement de la documentation là dessus.&lt;/p&gt;
&lt;p&gt;L’URL de redirection devra simplement être configurée pour atterrir sur &lt;code&gt;https://url-du-proxy/oauth2/callback&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;J’ai ensuite réalisé quelques modifications au niveau de l’objet &lt;code&gt;deployment&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;edit&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;oauth2-proxy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;J’ai uniquement modifié les arguments de démarrage du conteneur&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;automountServiceAccountToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--provider=azure&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--azure-tenant=${tenant_id}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--cookie-domain=recette.bar.fr&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--email-domain=bar.fr&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--oidc-issuer-url=https://login.microsoftonline.com/${tenant_id}/v2.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--reverse-proxy=true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--whitelist-domain=.recette.bar.fr&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--http-address=0.0.0.0:4180&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--https-address=0.0.0.0:4443&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--metrics-address=0.0.0.0:44180&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--config=/etc/oauth2_proxy/oauth2_proxy.cfg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Parmi les éléments importants vous devrez configurez votre identifiant de tenant Azure (remplacer la valeur &lt;code&gt;${tenant_id}&lt;/code&gt; par votre valeur).&lt;/p&gt;
&lt;p&gt;Le &lt;code&gt;--cookie-domain&lt;/code&gt; permet de préciser la portée de votre cookie. Il faut évidemment que le proxy soit lui même dans cette portée.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--reverse-proxy&lt;/code&gt; permet de préciser à OAuth2 proxy qu’il est lui même derrière un reverse proxy, dans notre cas un ingress nginx.&lt;/p&gt;
&lt;p&gt;La partie &lt;code&gt;--whitelist-domain&lt;/code&gt; permet de spécifier les domaines qui sont safe pour une redirection. On verra juste après que c’est l’ingress cible qui demande où doit se faire la redirection. Si celle ci n’est pas considérée comme acceptable par OAuth2 proxy, il ne redirigera pas.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;metrics-address&lt;/code&gt; permet de définir l’URL où alimenter Prometheus.&lt;/p&gt;
&lt;p&gt;L’étendue des possibles en matière de configuration est vaste, je vous invite à consulter &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/"&gt;la documentation associée&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Configuration de l’ingress applicatif&lt;/h3&gt;
&lt;p&gt;Il nous reste maintenant la partie la plus simple qui consiste à configurer l’ingress de notre application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$mon&lt;/span&gt;-namespace&lt;span class="w"&gt; &lt;/span&gt;annotate&lt;span class="w"&gt; &lt;/span&gt;ingress&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;-ingress&lt;span class="w"&gt; &lt;/span&gt;nginx.ingress.kubernetes.io/auth-response-headers&lt;span class="o"&gt;=&lt;/span&gt;x-auth-request-user,x-auth-request-email
kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$mon&lt;/span&gt;-namespace&lt;span class="w"&gt; &lt;/span&gt;annotate&lt;span class="w"&gt; &lt;/span&gt;ingress&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;-ingress&lt;span class="w"&gt; &lt;/span&gt;nginx.ingress.kubernetes.io/auth-signin&lt;span class="o"&gt;=&lt;/span&gt;https://url-du-proxy/oauth2/start?rd&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$scheme&lt;/span&gt;://&lt;span class="nv"&gt;$host$escaped_request_uri&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$mon&lt;/span&gt;-namespace&lt;span class="w"&gt; &lt;/span&gt;annotate&lt;span class="w"&gt; &lt;/span&gt;ingress&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;-ingress&lt;span class="w"&gt; &lt;/span&gt;nginx.ingress.kubernetes.io/auth-url&lt;span class="o"&gt;=&lt;/span&gt;https://url-du-proxy/oauth2/auth
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Je le mentionnais plus haut, le paramètre &lt;code&gt;rd&lt;/code&gt; est important, c’est lui qui déterminera l’URL de redirection. Si vous ne le précisez pas vous serez correctement authentifié mais vous resterez sur le proxy.&lt;/p&gt;
&lt;p&gt;C’est tout côté configuration, votre ingress applicatif est maintenant protégé par votre proxy qui assurera l’authentification avec votre Active Directory.&lt;/p&gt;
&lt;p&gt;Nous avons ici présenté un cas utilisant Azure Active Directory mais OAuth2 proxy est évidemment compatible avec tout un tas d’autre SSO.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Retour sur la conférence Cloud Nord 2023</title>
    <id>https://martin.catty.fr/blog/retour-sur-la-conference-cloud-nord-2023/</id>
    <link href="https://martin.catty.fr/blog/retour-sur-la-conference-cloud-nord-2023/" rel="alternate"/>
    <updated>2023-10-24T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;&lt;a href="/blog/fusionner-des-states-terraform-et-deplacer-les-ressources-importees/"&gt;Dans mon article sur la fusion de state terraform&lt;/a&gt; je vous avais laissé sur votre faim en vous promettant un article sur la Cloud Nord 2023.&lt;/p&gt;
&lt;h3&gt;Cloud Nord 2023&lt;/h3&gt;
&lt;p&gt;Le nom de la conférence pose le tableau, cette 3ème édition se déroulait à Euratechnologies avec au programme tout un tas de conférences couvrant des sujets variés : infrastructure as code, packaging d'applications, IA, Kubernetes, API.&lt;/p&gt;
&lt;p&gt;Un agenda alléchant et pour lequel nous avions des attentes élevées, en espérant notamment avoir droit à des sujets bien techniques.&lt;/p&gt;
&lt;p&gt;Avec Jonathan et Nicolas nous nous sommes donc répartis les conférences pour en couvrir un maximum de sujets.&lt;/p&gt;
&lt;h3&gt;Ouverture&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Bienvenue à cloud Nord" src="/images/bienvenue_a_cloud_nord.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Après quelques aléas techniques, Sébastien Blanc nous a retracé quelques étapes clés de l'informatique en s'appuyant sur un vieux livre « Silicon Valley ».&lt;/p&gt;
&lt;p&gt;Il était amusant de découvrir les opinions de l'époque au travers de ce livre publié aux balbutiements de la tech. À cette époque les GAFAM n'en étaient pas encore, Cisco était une petite boite en devenir et on parlait déjà d'IA !&lt;/p&gt;
&lt;p&gt;L'occasion de nous rappeler que les tendances dont on entend parler tous les jours prennent vie après des années ou des dizaines d'années de travail et ne sortent pas de nulle part.&lt;/p&gt;
&lt;p&gt;La transformation en commodité de l'intelligence artificielle se fait par contre à un rythme infernal et Sébastien a pu le prouver en déployant dans le cloud une application embarquant un moteur d'IA capable de répondre à cette question fatidique : « Le welsh vient-il de Lille ?». On ne polémiquera pas sur la réponse apportée, l'IA a encore du chemin à parcourir 😊.&lt;/p&gt;
&lt;h3&gt;Refactoring Terraform&lt;/h3&gt;
&lt;p&gt;Terraform étant un nos couteaux suisses du quotidien Devops, j'étais curieux d'entendre ce retour d'expérience.&lt;/p&gt;
&lt;p&gt;Je n'ai pas été déçu, la présentation d'Arthur était très bien maitrisée, vivante. Mention spéciale aux slides, avec un support très lisible.&lt;/p&gt;
&lt;p&gt;Cette présentation était un condensé de retour d'expérience pour maintenir un code Terraform propre et bien organisé.&lt;/p&gt;
&lt;p&gt;Cela passe par l'étape fatidique du refactoring et du déplacement de ressources associées. Une des solutions proposée par Terraform est l'usage de directive moved. Toutefois, sur des states un peu volumineux, leur écriture manuelle peut vite s'avérer fastidieuse.&lt;/p&gt;
&lt;p&gt;Arthur a donc introduit son propre outil &lt;a href="https://tfautomv.dev/"&gt;tfautomv&lt;/a&gt; dont l'objectif est de générer automatiquement ces directives. Il fonctionne en comparant les ressources 2 à 2 pour définir une correspondance entre source et destination. À coup sûr un outil à tester dès que l'occasion se présentera.&lt;/p&gt;
&lt;p&gt;La présentation ne s'attardait pas que sur cet outil mais également sur des bonnes pratiques et des conventions mise en place par le présentateur et son équipe.&lt;/p&gt;
&lt;p&gt;Entre autre, une directive à laquelle j'adhère consiste à garder ses modules racines légers en évitant de repasser l'intégralité des variables en top down.&lt;/p&gt;
&lt;p&gt;Une astuce proposée consiste à utiliser des modules « purs » utilisant les outputs pour ré-import en tant que constante dans d'autre modules.&lt;/p&gt;
&lt;p&gt;Un très bon talk, technique et concret, comme on les aime.&lt;/p&gt;
&lt;h3&gt;Continuous Container Checkup&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Tableau de bord grafana Continuous Container Checkup" src="/images/tableau_de_bord_grafana_continuous_container_checkup.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Cette présentation était organisée comme un jeu de rôle entre d'une part un développeur peu consciencieux (en mode je déploie mon app et je ne m'en soucie plus) et un autre développeur orienté run.&lt;/p&gt;
&lt;p&gt;Les deux intervenants d'Ubisoft voulaient nous présenter leurs tableaux de bord maisons conçus avec Grafana et remontant des métriques sur l'état de sécurité des applicatifs tournant dans leur cluster.&lt;/p&gt;
&lt;p&gt;En agrégeant les résultats de tout un tas d'outils (&lt;a href="https://github.com/anchore/syft"&gt;syft&lt;/a&gt;, &lt;a href="https://endoflife.date/"&gt;endoflife.date&lt;/a&gt;, &lt;a href="https://ecosyste.ms/"&gt;ecosystems&lt;/a&gt;, &lt;a href="https://github.com/anchore/grype"&gt;grype&lt;/a&gt;, &lt;a href="openvex"&gt;openvex&lt;/a&gt;…) ils ont pu collecter les informations nécessaires pour donner des indicateurs de santé sur chacune des images tournant dans leur cluster.&lt;/p&gt;
&lt;p&gt;Couplé à la puissance de Grafana on obtient une information très lisible (car graphique) avec des possibilités de filtrage puissantes.&lt;/p&gt;
&lt;p&gt;L'outil est très intéressant de par sa portée globale, on collecte l'information dans la zone d'exécution, ici Kubernetes, ce qui permet d'analyser n'importe quel projet qu'on viendrait déployer, quelle que soit sa topologie.&lt;/p&gt;
&lt;p&gt;Les équipes ont prévu de l'open sourcer sans toutefois donner de date, on attend ça avec impatience.&lt;/p&gt;
&lt;h3&gt;Composants internes de Kubernetes&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Composants internes de Kube" src="/images/composants_internes_de_kube.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Après la pause déjeuner on rattaquait avec la présentation de Denis Germain sur les composants internes de Kubernetes.&lt;/p&gt;
&lt;p&gt;Très didactique, la présentation déroulait une installation de Kubernetes (en 1.27 alpha s'il vous plait 😊), composant par composant : etc, scheduler, controller-manager.&lt;/p&gt;
&lt;p&gt;Le fait que le déroulé de la démo soit assez simple (la plupart du temps exécuter les binaires), montre à quelle point l'architecture de Kubernetes est bien pensée et orientée composants ; chacun ayant son propre périmètre de responsabilité mais pour autant indispensable au fonctionnement de l'ensemble de la chaine.&lt;/p&gt;
&lt;p&gt;Au passage, c'était l'occasion de reparler de Cilium (utilisé en tant que CNI) et d'eBPF (sur lequel s'appuie Cilium) dont on aura l'occasion de reparler sur le blog.&lt;/p&gt;
&lt;p&gt;Un mot a également été dit sur les objets de type &lt;code&gt;Gateway API&lt;/code&gt; (à ne pas confondre avec &lt;code&gt;API Gateway&lt;/code&gt;), qui devrait à terme prendre le pas sur ceux de type &lt;code&gt;Ingress&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;L'intelligence artificelle expliquée en 20 minutes&lt;/h3&gt;
&lt;p&gt;&lt;img alt="IA expliquée par un dev" src="/images/ia_expliquee_par_un_dev.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Sans transition me voilà dans une salle bondée pour assister un speed talk sur l'IA.&lt;/p&gt;
&lt;p&gt;Stéphane, développeur, nous a présenté l'IA sous l'angle de ce qu'il en a compris au gré de ses investigations et tests.&lt;/p&gt;
&lt;p&gt;La présentation situait les différents types d'intelligence artificielle (ANI : artificial narrow intelligence, AGI : artificial general intelligence, ASI : artificial super intelligence).&lt;/p&gt;
&lt;p&gt;L'occasion également de rappeler que l'intelligence artificielle ne peut pas partir de rien et que la qualité des données en entrée est l'un des éléments clés. Sauf que ces données sont souvent qualifiées par des … humains.&lt;/p&gt;
&lt;p&gt;L'IA est d'ailleurs capable de fonctionner en mode supervisé et non supervisé. Dans le premier elle sera guidé par l'humain pour confirmer ou infirmer ses « choix ». C'est notamment ce qui permet l'apprentissage par renforcement.&lt;/p&gt;
&lt;p&gt;En non supervisé l'IA va tenter de découvrir des patterns (les objets qui ressemblent à des voitures sont sûrement également des voitures). Ce mode est intéressant pour pré-qualifier et segmenter des gros volumes de données existants.&lt;/p&gt;
&lt;p&gt;Sur les aspects de type « Comment ça marche ? » on se rend compte que l'IA se base principalement sur des approches probabilistes en convertissant les données d'entrée en vecteur mathématiques.&lt;/p&gt;
&lt;p&gt;C'est aussi ce qui explique que les calculs soient bien plus performants avec des GPU, conçus pour traiter ce type d'information, qu'avec des CPU.&lt;/p&gt;
&lt;h3&gt;Créer des règles d'autoscaling efficace avec Kubernetes&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Keptn" src="/images/keptn.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Pour cette présentation en forme de REX on retrouve Henrik Rexed, déjà croisé lors de sa présentation « L'art de l'observabilité avec Open Telemetry ».&lt;/p&gt;
&lt;p&gt;Cette fois il s'agissait d'un retour d'expérience d'une approche finops. Au-delà de présenter des outils d'optimisation il s'agissait avant tout d'analyser des problématiques de performance dans la cadre d'une réduction de coûts.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.opencost.io/"&gt;Opencost&lt;/a&gt; a bien sûr était un des points d'entrée dans l'analyse des ressources mais finalement pas le centre de cette présentation.&lt;/p&gt;
&lt;p&gt;Il s'agissait avant tout de comprendre comment optimiser l'usage des ressources. Lors de la livraison d'un projet, un réflexe naturel est de surdimensionner les ressources pour garantir un fonctionnement optimal et d'optimiser ensuite (quand on n'oublie pas de le faire !).&lt;/p&gt;
&lt;p&gt;Pour maximiser son taux d'usage dans un contexte Kubernetes, il s'agit d'avoir un nombre de pod adapté à la charge et capable de scaler automatiquement.&lt;/p&gt;
&lt;p&gt;Kubernetes dispose évidemment d'une API pour la partie HPA (horizontal pod autoscaling) mais avec un nombre de métriques assez restreint : CPU et mémoire.&lt;/p&gt;
&lt;p&gt;Henrik nous démontre avec un exemple concret que ces métriques peuvent s'avérer insuffisantes, notamment lorsqu'on a posé des limites sur ses ressources. Dans son cas, le CPU des nodes sur lesquelles tournait l'application était la plupart du temps idle. L'occasion de relancer le débat sur &lt;a href="https://home.robusta.dev/blog/stop-using-cpu-limits"&gt;l'intérêt des limites de CPU&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il s'avère également que même lorsqu'elles sont intéressantes, ces métriques peuvent arriver trop tard.&lt;/p&gt;
&lt;p&gt;L'autoscaling n'étant pas instantané on veut idéalement que le dimensionnement s'anticipe autant que possible. C'est à ce moment que l'outil &lt;a href="https://lifecycle.keptn.sh/"&gt;Keptn&lt;/a&gt; a été introduit. Celui-ci permet de disposer de davantage de métriques en entrée pour prendre les bonnes décisions de scaling.&lt;/p&gt;
&lt;p&gt;En l'occurrence il s'agissait ici de s'appuyer sur le nombre de requêtes entrantes pour déterminer le dimensionnement à mettre en face.&lt;/p&gt;
&lt;p&gt;Finalement une présentation très dense et bien rythmée avec un cas concret, on a adoré.&lt;/p&gt;
&lt;h3&gt;Documenter et standardiser ses API avec AsyncAPI&lt;/h3&gt;
&lt;p&gt;&lt;img alt="AsyncAPI" src="/images/asyncapi.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Dans cette dernière présentation de la journée, Ludovic nous a parlé de son parcours avec Async API. Un projet qu'il utilise au quotidien dans le cadre professionnel et auquel il a directement contribué.&lt;/p&gt;
&lt;p&gt;Async API est un standard très proche de OpenAPI, dans sa conception et sa documentation, à tel point que passer de l'un à l'autre n'est pas vraiment dépaysant.&lt;/p&gt;
&lt;p&gt;Avec des API asynchrones on sort de la logique requête / réponse et on bascule dans une logique client / serveur avec des connexions qui peuvent rester ouvertes et des flux qui ne sont pas bloqués dans l'attente de la réponse.&lt;/p&gt;
&lt;p&gt;On va donc plutôt utiliser des websocket ou du gRPC. AsyncAPI a l'ambition de devenir le standard de facto, en lieu et place de OpenAPI (tout un programme !). Ils ont récemment monté un partenariat avec Postman qui dédie un certain nombre de ses employés pour travailler sur le standard et son intégration dans Postman.&lt;/p&gt;
&lt;p&gt;Lorsqu'on travaille avec des outils tels que OpenAPI ou AsyncAPI il est préférable d'opter pour une logique « contract first » (vs code first).&lt;/p&gt;
&lt;p&gt;En clair, on va prendre le temps en amont de définir le format de son API, les paramètres d'entrée, les formats de retour avant de travailler sur l'implémentation.&lt;/p&gt;
&lt;p&gt;L'immense avantage de ce type d'approche et de faciliter la mise en place de mock et potentiellement paralléliser le développement de l'API et de son ou ses clients (qu'on en maitrise le développement ou non).&lt;/p&gt;
&lt;p&gt;AsyncAPI permet l'exposition de schémas sous différents formats : le bien connu JSON schema mais également RAML ou Avro.&lt;/p&gt;
&lt;p&gt;À noter que la version 3 de AsyncAPI est en cours de construction. Difficile de dire si on est proche ou non d'une finalisation, &lt;a href="https://www.asyncapi.com/blog/async-api-spec-3.0-release"&gt;le dernier article&lt;/a&gt; annonçant une arrivée datant de mars 2022.&lt;/p&gt;
&lt;p&gt;Au final un sujet très intéressant et parfaitement maitrisé. Utilisateurs de longue date d'OpenAPI nous allons nous pencher plus sérieusement sur AsyncAPI.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;On en retiendra une journée enrichissante, des conférences très intéressantes et des présentations bien maitrisées.&lt;/p&gt;
&lt;p&gt;Le superbe cadre d'Euratechnologies aura compensé le seul bémol de la journée : le manque de café !&lt;/p&gt;
&lt;p&gt;On a hâte d'assister à la prochaine édition.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Fusionner des states terraform et déplacer les ressources importées</title>
    <id>https://martin.catty.fr/blog/fusionner-des-states-terraform-et-deplacer-les-ressources-importees/</id>
    <link href="https://martin.catty.fr/blog/fusionner-des-states-terraform-et-deplacer-les-ressources-importees/" rel="alternate"/>
    <updated>2023-10-18T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;La vie de développeur est faite de plusieurs craintes : devoir gérer un conflit git, régler un problème de cache et gérer un state terraform à la main par exemple.&lt;/p&gt;
&lt;p&gt;Aujourd’hui on va s’attaquer à ce dernier avec un cas concret.&lt;/p&gt;
&lt;h3&gt;Pourquoi diable vouloir fusionner des states Terraform ?&lt;/h3&gt;
&lt;p&gt;Imaginez que vous ayez une application pour laquelle vous devez provisionner une infrastructure, mettons sur AWS.&lt;/p&gt;
&lt;p&gt;L’application tourne dans un cluster EKS provisionné à part, dans son namespace et utilise un utilisateur dédié dans Rancher.&lt;/p&gt;
&lt;p&gt;La vie est belle, vos ressources sont provisionnées « à plat » à grand coup de &lt;code&gt;resource x&lt;/code&gt; dans vos scripts terraform.&lt;/p&gt;
&lt;p&gt;Mais voilà que l’application devient multi client et chaque client a besoin de sa « stack », avec son namespace, son bucket etc.&lt;/p&gt;
&lt;p&gt;Pour faire simple vous copiez collez vos ressources en changeant le nom et la configuration.&lt;/p&gt;
&lt;p&gt;Tout cela fonctionne très bien mais le nombre de stack grandit et vous vous retrouvez avec 5 stacks et un code terraform ingérable où la moindre modification doit être répliquée autant de fois.&lt;/p&gt;
&lt;p&gt;Ce qui permettait d’aller vite au début devient maintenant un caillou dans la chaussure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;"rancher2_namespace"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;"project_a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;"rancher2_namespace"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;"project_b"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;"rancher2_namespace"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;"project_c"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Comme on le voit, chacune des ressources (bucket, user…) est dupliquée.&lt;/p&gt;
&lt;p&gt;Armé de votre courage, vous décidez de mettre en place un module terraform, qui vous permettra de définir de nouvelles stacks en trois coups de cuiller à pot.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;"stack"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./modules/stack"&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;for_each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local.stacks&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;each.value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;each.value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"secrets"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;locals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;stacks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;var.default_secrets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;var.stacks_secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Génial, c’est simple à maintenir et la complexité est déportée dans le module à un seul endroit. Plus de doublons.&lt;/p&gt;
&lt;p&gt;Le problème c’est que vous avez déjà provisionné 5 stacks avant de vous décider à mettre en place un module.&lt;/p&gt;
&lt;p&gt;Idéalement il faut que ces stacks ré-intègrent votre module. Malheureusement les fichiers de states sont non seulement différents (ce sont des fichiers distincts) mais les ressources ne sont pas au même format.&lt;/p&gt;
&lt;p&gt;Là où vous aviez &lt;code&gt;rancher2_project_role_template_binding.project_a&lt;/code&gt; vous devriez maintenant avoir &lt;code&gt;module.stack["project_a"].rancher2_project_role_template_binding.stack&lt;/code&gt;, vos stacks étant gérées dans une map.&lt;/p&gt;
&lt;p&gt;Nous allons voir comment gérer cette transition.&lt;/p&gt;
&lt;h3&gt;Sécurisez l’existant&lt;/h3&gt;
&lt;p&gt;On commence par les précautions d’usage, c’est à dire faire un backup des states.&lt;/p&gt;
&lt;p&gt;On appellera &lt;code&gt;original&lt;/code&gt; celui qui a été géré sans module et &lt;code&gt;destination&lt;/code&gt; celui avec.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# dans le projet sans module&lt;/span&gt;
tf&lt;span class="w"&gt; &lt;/span&gt;state&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;original.tfstate
&lt;span class="c1"&gt;# dans le projet avec module&lt;/span&gt;
tf&lt;span class="w"&gt; &lt;/span&gt;state&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;destination.tfstate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Faites un backup en lieu sûr de ces fichiers pour disposer d’exemplaires originaux et travaillez sur une copie.&lt;/p&gt;
&lt;p&gt;Les fichiers d’états sont de simples fichiers texte, toutefois je vous déconseille vivement de vous essayer à des modifications manuelles dans ces fichiers.&lt;/p&gt;
&lt;p&gt;Commençons par lister le contenu de notre state original.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;terraform&lt;span class="w"&gt; &lt;/span&gt;state&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;-state&lt;span class="o"&gt;=&lt;/span&gt;original.tfstate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;nous donne :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rancher2_cluster_role_template_binding.project_a
rancher2_cluster_role_template_binding.project_b
rancher2_cluster_role_template_binding.project_c
rancher2_cluster_role_template_binding.project_d
rancher2_cluster_role_template_binding.project_e
rancher2_cluster_role_template_binding.project_f
rancher2_global_role_binding.project_a
rancher2_global_role_binding.project_b
rancher2_global_role_binding.project_c
rancher2_global_role_binding.project_d
rancher2_global_role_binding.project_e
rancher2_global_role_binding.project_f
rancher2_namespace.project_a
rancher2_namespace.project_b
rancher2_namespace.project_c
rancher2_namespace.project_d
rancher2_namespace.project_e
rancher2_namespace.project_f
rancher2_project.project_a
rancher2_project.project_b
rancher2_project.project_c
rancher2_project.project_d
rancher2_project.project_e
rancher2_project.project_f
rancher2_project_role_template_binding.project_a
rancher2_project_role_template_binding.project_b
rancher2_project_role_template_binding.project_c
rancher2_project_role_template_binding.project_d
rancher2_project_role_template_binding.project_e
rancher2_project_role_template_binding.project_f
rancher2_registry.project_a
rancher2_registry.project_b
rancher2_registry.project_c
rancher2_registry.project_d
rancher2_registry.project_e
rancher2_registry.project_f
rancher2_secret_v2.project_a_administration
rancher2_secret_v2.project_a_aws_s3
rancher2_secret_v2.project_a_database
rancher2_secret_v2.project_a_deepl
rancher2_secret_v2.project_a_froala
rancher2_secret_v2.project_a_google
rancher2_secret_v2.project_a_mailjet
rancher2_secret_v2.project_b_administration
rancher2_secret_v2.project_b_aws_s3
rancher2_secret_v2.project_b_database
rancher2_secret_v2.project_b_google
rancher2_secret_v2.project_c_administration
rancher2_secret_v2.project_c_aws_s3
rancher2_secret_v2.project_c_database
rancher2_secret_v2.project_c_google
rancher2_secret_v2.project_d_administration
rancher2_secret_v2.project_e_administration
rancher2_secret_v2.project_e_aws_s3
rancher2_secret_v2.project_e_database
rancher2_secret_v2.project_e_google
rancher2_secret_v2.project_f_administration
rancher2_secret_v2.project_f_aws_s3
rancher2_secret_v2.project_f_database
rancher2_secret_v2.project_f_google
rancher2_secret_v2.project_f_mailjet
rancher2_user.project_a
rancher2_user.project_b
rancher2_user.project_c
rancher2_user.project_d
rancher2_user.project_e
rancher2_user.project_f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ouch ! Ce fichier nous liste l’ensemble des ressources dans notre state. Forcément nous retrouvons chaque ressource démultipliée (1 fois par projet).&lt;/p&gt;
&lt;p&gt;Il va falloir déplacer ces ressources une à une avec &lt;code&gt;tf state mv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On ne se laisse pas abattre et on va créer un petit script pour nous aider. Celui ci est très simple et va migrer les ressources d’un fichier de state à un autre.&lt;/p&gt;
&lt;p&gt;Ce qu’on souhaite c’est que notre code à base de stack ait connaissance des ressources qui avaient été crées hors stack afin de les ré-intégrer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;state&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;terraform&lt;span class="w"&gt; &lt;/span&gt;state&lt;span class="w"&gt; &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;-state&lt;span class="o"&gt;=&lt;/span&gt;original.tfstate&lt;span class="w"&gt; &lt;/span&gt;-state-out&lt;span class="o"&gt;=&lt;/span&gt;destination.tfstate&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&lt;span class="k"&gt;$(&lt;/span&gt;terraform&lt;span class="w"&gt; &lt;/span&gt;state&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;-state&lt;span class="o"&gt;=&lt;/span&gt;original.tfstate&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Une fois fait, on va se retrouver avec notre fichier &lt;code&gt;destination.tfstate&lt;/code&gt; contenant l’intégralité du state. On peut le vérifier avec un &lt;code&gt;terraform state list -state=destination.tfstate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;À noter que terraform va créer des fichiers de state intermédaires pour chacune des opérations, permettant un rollback éventuel. Étant donné qu’on travaille sur des fichiers locaux le risque est ici limité.&lt;/p&gt;
&lt;p&gt;En l’état ce qu’on vient de faire s’apparente à un merge des fichiers de state.&lt;/p&gt;
&lt;p&gt;Maintenant il faut que nous mettions les nouvelles ressources au bon format. Je vous conseille également d’outiller la démarche avec des scripts, au moins par type de ressources, si le nombre de ressources à migrer est important.&lt;/p&gt;
&lt;p&gt;En terme de commandes finales elles s’apparenteront à cela :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tf&lt;span class="w"&gt; &lt;/span&gt;state&lt;span class="w"&gt; &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;-state&lt;span class="o"&gt;=&lt;/span&gt;destination.tfstate&lt;span class="w"&gt; &lt;/span&gt;rancher2_user.project_a&lt;span class="w"&gt; &lt;/span&gt;module.stack&lt;span class="se"&gt;\[\"&lt;/span&gt;project_a&lt;span class="se"&gt;\"\]&lt;/span&gt;.rancher2_user.stack
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Une fois que les ressources ont été migrées il nous reste à ajouter les instructions dans notre module, comme si les stacks importées avaient été créées avec.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;"stack"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./modules/stack"&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;for_each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local.stacks&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;each.value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;each.value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"secrets"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;locals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;stacks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;var.default_secrets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;var.stacks_secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"project_a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project_a"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;var.default_secrets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;var.stacks_secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"project_a"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;C’est le moment de vérité, il reste à vérifier qu’on a bien fait les choses :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mv&lt;span class="w"&gt; &lt;/span&gt;destination.tfstate&lt;span class="w"&gt; &lt;/span&gt;nom_utilise_dans_votre_projet.tfstate
tf&lt;span class="w"&gt; &lt;/span&gt;state&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;nom_utilise_dans_votre_projet.tfstate
tf&lt;span class="w"&gt; &lt;/span&gt;plan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si tout est en ordre notre &lt;code&gt;tf plan&lt;/code&gt; ne devrait produire aucun changement (pas de destroy, add ou update). Si vous avez tout cassé, remettez le state original, retirez le code ajouté et recommencez.&lt;/p&gt;
&lt;p&gt;À noter qu’ici je donne l’exemple de state différents, si j’avais eu à gérer des mouvements de ressources faisant déjà partie d’un même projet et d’un même state j’aurai sans doute utilisé des &lt;a href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/move-config"&gt;instructions &lt;code&gt;moved&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;J’aurai peut être même utilisé &lt;a href="https://tfautomv.dev/"&gt;tfautomv&lt;/a&gt; que j’ai eu l’occasion de découvrir à Cloud Nord 2023 et dont je vous reparlerai dans l’article de compte rendu des conférences 😊.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Retour sur la conférence We Love Speed 2023</title>
    <id>https://martin.catty.fr/blog/retour-sur-la-conference-we-love-speed-2023/</id>
    <link href="https://martin.catty.fr/blog/retour-sur-la-conference-we-love-speed-2023/" rel="alternate"/>
    <updated>2023-05-26T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Entouré de mes acolytes Gaëtan et Nicolas nous étions mercredi 10 mai à la conférence We Love Speed. Cette conférence itinérante s'arrêtait cette année à Paris, à l'espace Saint Martin.&lt;/p&gt;
&lt;p&gt;Les sujets des conférences gravitent évidemment tous autour de la performance et plus précisément de la web performance. Le principal focus se trouve notamment sur les aspects front. Je pense que c'est d'ailleurs un axe qui pourrait évoluer pour couvrir des pans plus larges de la performance (infrastructure, pattern côté back-end…).&lt;/p&gt;
&lt;p&gt;On peut toutefois se féliciter d'avoir ce type de conférence avec des intervenants d'excellent niveau.&lt;/p&gt;
&lt;p&gt;Dans cet article, nous allons donc vous retracer les différentes conférences et ateliers que nous avons pu voir et ce que nous en avons retenu.&lt;/p&gt;
&lt;h3&gt;Dompter l’Impact en Webperf des Scripts Tiers&lt;/h3&gt;
&lt;p&gt;Andy Davies nous parle de l’impact des scripts third party dans la performance web.&lt;/p&gt;
&lt;p&gt;Si les fonctionnalités offertes (analytics, RUM, contenu) ne sont pas à remettre en cause, il est nécessaire de trouver un équilibre.&lt;/p&gt;
&lt;p&gt;Un bon outil pour obtenir une cartographie des requêtes réalisées pour un site donné : &lt;a href="https://requestmap.herokuapp.com/"&gt;Request map generator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Andie propose la classification des contenus suivante pour déterminer quand charger quoi :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Classification des contenus" src="/images/classification_des_contenus.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Il nous ouvre également les yeux sur les problématiques de vie privée ouvertes par les scripts tiers. La fuite d’IP vers un domaine tiers sans consentement a notamment été récemmetn l’objet d’un procès en Allemagne.&lt;/p&gt;
&lt;p&gt;Il s’interroge sur l’intérêt de proposer des API de consentement intégrées au navigateur, ce qui leur conférerait encore plus de pouvoirs. Les CDN également commencent à proposer des alternatives (ex : cloudflare zaraz).&lt;/p&gt;
&lt;p&gt;Par ailleurs les chargements sur des sites tiers, aussi rapides soient ils, s’avèrent souvent plus lents que des chargements locaux.&lt;/p&gt;
&lt;p&gt;Tous les scripts third party ne sont pas égaux et ils doivent également être monitorés. Il est nécessaire de définir précisément ce qu’on veut mesurer (taille, nombre de requêtes…). Par exemple google tag manager est très lourd à charger.&lt;/p&gt;
&lt;p&gt;Certaines lib (ex : postscribe) implémenent des fonctionnalités qui sont potentiellement très peu utilisées et avec des impacts majeurs en terme de performance (dans ce cas &lt;code&gt;document.write&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Enfin, le simple fait de ré-ordonner les tags peut parfois amener des gains de performance significatifs. Ces gains sont d’ailleurs souvent synonymes de revenus plus importants.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Amélioration des revenus avec l’amélioration de la performance" src="/images/amelioration_des_revenus_avec_lamelioration_de_la_performance.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Les Protocoles Web pour les devs front-end&lt;/h3&gt;
&lt;p&gt;Dans cette conférence de Robin Marx, qui offre un parallèle surprenant avec le seigneur des anneaux, il est question (d’épées et) de protocoles, principalement d’HTTP.&lt;/p&gt;
&lt;p&gt;Actuellement nous en sommes à HTTP/3 (basé sur QUIC), qui offre l’avantage d’être pleinement rétro-compatible avec HTTP/2. L’activer pour un site compatible H2 est donc sans risque.&lt;/p&gt;
&lt;p&gt;&lt;img alt="http/1 vs http/2 vs http/3" src="/images/http1_vs_http2_vs_http3.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Robin se positionne du point de vue des CDN (il travaille pour l’un d’eux).&lt;/p&gt;
&lt;p&gt;Pour un CDN il est difficile de connaitre le nombre de paquets qu’il est possible d’envoyer.&lt;/p&gt;
&lt;p&gt;En effet, côté client, La taille d’un lien est fixe mais souvent partagée entre plusieurs connexions, qui vont et qui viennent. Il est donc difficile de connaitre précisément la capacité de la connexion à un instant T. Les CDN en sont donc réduits à l’estimer, de manière progressive, jusqu’à atteindre les limites, en doublant le nombre de paquets à chaque fois que le receveur accuse bonne réception de l’ensemble.&lt;/p&gt;
&lt;p&gt;Tous les CDN ont donc une logique de slow start avec des round trip qui chargent de plus en plus de données à chaque « tour ».&lt;/p&gt;
&lt;p&gt;Au delà de la taille qu’il est possible d’envoyer à chaque fois, le CDN doit également déterminer quels paquets envoyer dans quel ordre. Notamment arbitrer ce qui doit être priorisé entre le HTML, le JS et le CSS.&lt;/p&gt;
&lt;p&gt;Faut il envoyer un type de ressource complètement ou envoyer des morceaux de chaque type à chacune des itérations ?&lt;/p&gt;
&lt;p&gt;Il s’avère qu’il est mieux d’envoyer un type de ressource complètement, notamment pour JS et CSS qui ne sont pas utilisables avant d’avoir été complètement envoyés. Il ne sont pas streamable comme certains types d’image par exemple.&lt;/p&gt;
&lt;p&gt;On découvre également qu’un grand nombre de serveurs n’appliquent pas les consignes demandées par les navigateurs en terme de priorités (Apache fait plutôt office de bon élève). Mais les navigateurs ne sont pas non plus d’accord entre eux sur la pondération des règles !&lt;/p&gt;
&lt;p&gt;Safari en prend pour son grade, souvent qualifié de nouvel Internet Explorer. Il gère notamment la priorité des JS exactement de la même façon quelqu’ils soient (head, body, defer), à l’exception de ceux en async qui se voient affectés une priorité moindre.&lt;/p&gt;
&lt;p&gt;De même, les images en preload ont une priorité moindre que sans l’attribut (sic). Au delà de l’aspect discutable de ces choix, ils portent à confusion pour les développeurs qui ne s’attendent pas à ces comportements et qui doivent gérer des différences selon les navigateurs.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Gestion des priorités par les navigateurs" src="/images/gestion_des_priorites_par_les_navigateurs.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Un nouvel attribut, &lt;code&gt;fetchpriority&lt;/code&gt; permet aux développeurs d’expliciter la priorité à donner aux différents éléments.&lt;/p&gt;
&lt;p&gt;Robin donne l’exemple des carrousel où la priorité de la première image se doit d’être haute quand celle des suivantes peut être basse.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Gestion des priorités sur les éléments" src="/images/gestion_des_priorites_sur_les_elements.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Finalement l’auteur nous livre ce qui pourrait résumer en une phrase l’ensemble des conférences : « Le chargement de ressources n’est ni cohérent ni prédictible selon les conditions (navigateur, serveur, réseau…). Simplement l’observer dans son navigateur n’est pas suffisant. »&lt;/p&gt;
&lt;p&gt;&lt;img alt="Le conseil de la journée" src="/images/le_conseil_de_la_journee.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Mesure ou Meurs&lt;/h3&gt;
&lt;p&gt;Dans un superbe décor, Jean-Pierre Vincent fait un focus sur webpagetest pour mesurer la performance des sites.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Webpagetest" src="/images/webpagetest.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Il pointe du doigt l’effet pervers des systèmes, comme Lightouse, qui donne des notes sur des critères donnés de manière automatique, ce qui incite les équipes à obtenir la meilleure note possible sans nécessairement comprendre ce qu’elles sont entrain de faire.&lt;/p&gt;
&lt;p&gt;On y apprend l’intérêt de savoir décrypter un waterfall, en commencant par le TTFB (Time to first byte), c’est à dire le temps d’attente avant de recevoir la réponse du serveur.&lt;/p&gt;
&lt;p&gt;En performance, la taille de la ressource n’est pas forcément le critère numéro 1, c’est surtout l’impact sur l’utilisateur. Paradoxalement on peut avoir des TTFB long pour des ressources légères et court pour des ressources lourdes avec des impacts utilisateurs inverses à ce qu’on pourrait imaginer au premier abord.&lt;/p&gt;
&lt;p&gt;Un autre élément clé de la performance est le LCP (Largest Contentful Paint), à savoir l’élément ayant la plus grande surface visible à l’écran.&lt;/p&gt;
&lt;p&gt;Le LCP est variable selon le terminal qui fait le rendu. Webpagetest indique en vert le LCP, ce qui permet se savoir à quel moment exactement il est rendu. Une fois qu’on connait cet élément on va pouvoir libérer le chemin critique afin de faire en sorte qu’il soit chargé le plus tôt possible.&lt;/p&gt;
&lt;p&gt;Pour mesurer la réactivité des sites, un indicateur historique est le First Input Delay (FID), qui n’est pas parfait car il mesure le délai entre un clic et un callback.&lt;/p&gt;
&lt;p&gt;Il tend à être remplacé par le Interaction to next paint (INP) qui mesure le temps entre le clic et le prochain affichage. Ces deux indicateurs ne sont toutefois pas simulables par des outils.&lt;/p&gt;
&lt;p&gt;Le total blocking time est le temps passé entre le FCP et le Time to Interactive (TTI). Pour le réduire il est important de trouver les scripts consommateurs, par exemple en les groupant dans Chrome dev tools.&lt;/p&gt;
&lt;p&gt;Lighthouse peut également être utilisé en mode timespan, ce qui permet d’enregistrer les interactions et de détailler le temps passé.&lt;/p&gt;
&lt;h3&gt;Comment devenir un Sherlock Holmes de la Web Performance&lt;/h3&gt;
&lt;p&gt;Ludovic Lefebvre revient sur son expérience chez Peaksys (filiale tech de cdiscount).&lt;/p&gt;
&lt;p&gt;Avec 20M de visiteurs par mois on comprend que la performance est un enjeu.&lt;/p&gt;
&lt;p&gt;Notre orateur vient nous présenter la méthode Sherlock, qui vise à passer de données à informations puis connaissances et enfin sagesse.&lt;/p&gt;
&lt;p&gt;Les connaissances sont très précieuses car elles sont ré-utilisables dans des contextes similaires.&lt;/p&gt;
&lt;p&gt;Pour analyser une situation on va généralement commencer par les observations, suivies des hypothèses, des expérimentations et enfin des conclusions.&lt;/p&gt;
&lt;p&gt;Pour avoir une bonne visibilité il est important d’être bien outillé, notatement niveau RUM (Real User Monitoring) et également performance d’infrastructure, APM et non régression.&lt;/p&gt;
&lt;p&gt;Cette visibilité doit se compléter avec une vsion claire de tous les changements (contenu, infrastructure, déploiement…). Ludovic prend le cas concret d’une augmentation du LCP (Largest Contentful Paint).&lt;/p&gt;
&lt;p&gt;Pour comprendre ce qui c’était passé il a fallu s’appuyer sur les outils, notamment de RUM et le croiser avec l’historique des changements pour comprendre que cela résultait d’une opération de communication qui avait entrainé le retrait du bandeau sur le site. Cela a eu pour effet de dégrader le LCP.&lt;/p&gt;
&lt;p&gt;Enfin Ludovic nous offre un parallèle du biais du survivant avec l’anedocte bien connue des avions endommagés qui sont revenus à la base après la seconde guerre mondiale.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Le biais du survivant" src="/images/le_biais_du_survivant.webp" /&gt;&lt;/p&gt;
&lt;p&gt;La première réaction a été de renforcer les zones endommagées avant de comprendre que les zones à améliorer étaient celles qui n’étaient pas endommagées car relatives aux avions qui n’étaient pas revenus.&lt;/p&gt;
&lt;p&gt;Dans le web le constat est applicable dans la webperformance où les avions s’apparentent aux utilisateurs et le champ de bataille au tunnel de conversion.&lt;/p&gt;
&lt;p&gt;Il ne faut pas analyser les utilisateurs qui arrivent en bout de tunnel car ce sont ceux qui commandent et qui ont donc atteint le résultat escompté. Il faut au contraire se pencher sur tous les autres.&lt;/p&gt;
&lt;h3&gt;L'art de l'observabilité avec Open Telemetry&lt;/h3&gt;
&lt;p&gt;Un atelier proposée par un intervenant de Dynatrace, qui a su garder un bon focus sur Open Telemetry plutôt que sur Dynatrace, un excellent point.&lt;/p&gt;
&lt;p&gt;OpenTelemetry cible l'observalité (je comprends ce qui se passe) plutôt que le monitoring (je constate ce qui se passe).&lt;/p&gt;
&lt;p&gt;Il vise à standardiser la production de métriques, logs, traces, profiling. Il est né sur les cendres de la fusion entre OpenTracing et OpenCensus.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Les piliers de l’observabilité" src="/images/les_piliers_de_lobservabilite.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Ctete standardisation permet d'homogénéiser les pratiques, plutot que d'avoir des multitudes d'outils qui sont spécialisés dans leur domaine (réseau, application…) et pas nécessairement interopérables. Il faut toutefois garder en tête que c’est d’une part un standard vivant, avec des choses restant à formaliser et que le niveau des outils varie d’une stack technique à l’autre.&lt;/p&gt;
&lt;p&gt;&lt;img alt="La partie logs et prolifing ne sont pas encore complètement standardisés" src="/images/la_partie_logs_et_prolifing_ne_sont_pas_encore_completement_standardises.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Concrètement, on peut imaginer avec des traces formalisées suivre le parcours complet d'une requête d'un visiteur sur une app, depuis l'instrumentation du front jusqu'aux infrastructures et définir exactement quel temps il a passé dans quel composant afin de diagnostiquer les éventuelles erreurs / lenteurs.&lt;/p&gt;
&lt;p&gt;La télémétrie s’articule autour de deux composants clés : l’instrumentation et le collecteur. L’objectif de standardiser ce dernier est d’éviter d’avoir un collecteur spécifique par type de logs ou traces mais plutôt un générique et agnostique des plateformes ciblées.&lt;/p&gt;
&lt;p&gt;OpenTelemetry est actuellement le projet le plus dynamique porté par la CNCF derrière Kubernetes. Pas étonnant qu’il soit au cœur de notre plan d’action technique cette année.&lt;/p&gt;
&lt;h3&gt;Soft navigation&lt;/h3&gt;
&lt;p&gt;Yoav Weiss nous propose une conférence teintée d’humour sur la soft navigation. Par soft navigation on entend le contrôle de la navigation côté app, notamment dans le cadre des SPA (qui manipule la navigation et l’historique).&lt;/p&gt;
&lt;p&gt;Le fond du débat tournait donc autour de SPA (single page app) vs MPA (multiple page app). Yoav travaille sur Google Chrome. À la question quelle architecture est plus performante il répond : « on ne sait pas ». Globalement, mesurer la performance d’une SPA est toujours aussi complexe, bien que le sujet soit ouvert depuis plus de 10 ans.&lt;/p&gt;
&lt;p&gt;Du côté des implémenteurs de navigateur, la gestion de l’historique par les applications est complexe car assez tributaire de l’usage des framework ou lib sous jacente. Par exemple, certains vont déclencher &lt;code&gt;history.pushState&lt;/code&gt; dès que l’utilisateur interagit avec la page (click par exemple), d’autres une fois seulement que la nouvelle vue est rendue, ce qui rend complexe la détermination du point de départ.&lt;/p&gt;
&lt;p&gt;D’autre part les SPA ne font pas bon ménage avec les web core vitals. En effet on ne mesure que le premier chargement qui est généralement le plus lent, avec la promesse que la navigation secondaire sera plus rapide, mais invisible par le navigateur.&lt;/p&gt;
&lt;p&gt;Une nouvelle API voit le jour : Navigation API, qui est actuellement uniquement supportée par Chrome. Avec cette API on peut visualiser dans les chrome tools les même métriques que pour la navigation primaire et également disposer d’API de mesure de la performance (&lt;code&gt;PerformanceObserver&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Les API sont accessible derrières un flag experimental qu’il est possible d’activer dès à présent (&lt;code&gt;reportSoftNavs&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;Le top 10 des perles en webperformance&lt;/h3&gt;
&lt;p&gt;Stéphanie Rios nous offre un compilation des perles qu’il croise au quotidien.&lt;/p&gt;
&lt;p&gt;Le lazyloading c’est bien mais trop de lazyloading tue le lazyloading. La règle d’or : ne lazyloader que les images qui ne sont pas visibles dans le viewport.&lt;/p&gt;
&lt;p&gt;Si on lazyload une image qui est visible dans le viewport, on retarde le temps de chargement général. Soit on utilise une lib de lazyload et le chargement de l’image sera tributaire du chargement de cette lib, soit on utilise le lazyload natif et cette image sera dépriorisée.&lt;/p&gt;
&lt;p&gt;Il nous conseille également de privilégier les lib plutôt que le lazy load natif qu’il juge pas suffisamment « agressif ». En effet le lazyload natif peut charger des contenus qui ne seront potentiellement jamais vu, car accessible 2-3 viewport plus bas dans la page.&lt;/p&gt;
&lt;p&gt;Le defer peut parfois s’avérer contre productif. Chargé trop tôt dans le document il peut bloquer le téléchargement d’autres ressources.&lt;/p&gt;
&lt;p&gt;Les directives &lt;code&gt;@import&lt;/code&gt; CSS, notamment pour les google fonts peuvent également occasioner des blocages. On trouve également des fonts servies en OTF au lieu de WOFF2 (qui est lui un format compressé à privilégier).&lt;/p&gt;
&lt;p&gt;Les appels à des domaines externes sont parfois aussi sources de blocages. Des scripts third party sont parfois chargés de manières bloquantes sur des domaines externes, ce qui pose des problèmes en situation de mobilité. En plus de cela le bénéfice est parfois anecdotique dans le cadre de polyfill qui ne seront pas utilisés si la fonctionnalité est supportée nativement.&lt;/p&gt;
&lt;p&gt;Côté mise en cache des lib third party par l’usage d’un CDN, notre speaker nous rappelle que depuis quelques années la clé de cache des ressources externes est doublée avec le host de l’origine. Si une lib est chargée depuis un domaine A et qu’elle est également demandée sur un domaine B elle sera quand même rechargée.&lt;/p&gt;
&lt;p&gt;Certaines ressources manquent souvent à l’appel en terme de lazyload, on peut notamment citer le player youtube, très gourmand en ressources. Il est possible de le mettre dans une iframe et lazyloader cette dernière.&lt;/p&gt;
&lt;p&gt;On trouve souvent des images responsives qui ne le sont pas, par exemple la même image en desktop et en mobile ou des erreurs de media queries.&lt;/p&gt;
&lt;p&gt;Enfin il faut maintenant abolir les sprites, qui sont des concaténations d’image avec des références à la position de l’image à afficher. On se retrouve donc à charger des sprites potentiellement lourd, parfois sur mobile, pour utiliser seulement quelques images de ce dernier.&lt;/p&gt;
&lt;p&gt;Maintenant que l’on dispose de HTTP/2 qui permet de multiplexer les contenus sur une même connexion cela n’a plus d’intérêt.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Cette ultime conférence marquait la fin de cette belle édition. On en retiendra que la webperformance nécessite des piqures de rappel fréquentes sur des notions qui peuvent sembler acquises. Elle demande également un peu de subtilité pour des comportements qui peuvent parfois s’avérer contre-intuitifs.&lt;/p&gt;
&lt;p&gt;Enfin, la webperformance doit se construire avec les équipes de dév et pas à leur détriment ni après coup.&lt;/p&gt;
&lt;p&gt;On saluera également pour cette conférence une organisation soignée et jamais mise en défaut. Merci aux organisateurs !&lt;/p&gt;
&lt;p&gt;&lt;img alt="Merci" src="/images/merci.webp" /&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Accéder à un RPN SAN Scaleway dans une machine virtuelle Proxmox</title>
    <id>https://martin.catty.fr/blog/acceder-a-un-rpn-san-scaleway-dans-une-machine-virtuelle-proxmox/</id>
    <link href="https://martin.catty.fr/blog/acceder-a-un-rpn-san-scaleway-dans-une-machine-virtuelle-proxmox/" rel="alternate"/>
    <updated>2023-05-07T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;L’objectif de cet article est de présenter l’utilisation d’un stockage iSCSI chez Online / Scaleway en vue de rattacher des volumes réseaux à des machines virtuelles dans Proxmox.&lt;/p&gt;
&lt;p&gt;À noter que si vous n’utilisez pas Proxmox l’article peut quand même présenter un intérêt, toutefois je pars du postulat que vous avez un Proxmox fonctionnel avec quelques machines virtuelles.&lt;/p&gt;
&lt;h3&gt;Utiliser une interface privée sur chacun des machines virtuelles&lt;/h3&gt;
&lt;p&gt;Nous allons commencer par créer un groupe RPN v2 dans l’interface Online afin d’y ajouter les serveurs.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ajout d’un groupe RPN v2" src="/images/ajout_dun_groupe_rpn_v2.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Chacune des machines doit être dans le même VLAN afin qu’elles puissent communiquer ensemble. Ici l’identifiant du VLAN est 2001, vous pouvez choisir ce que vous voulez.&lt;/p&gt;
&lt;p&gt;Au passage nous ajoutons la compatibilité avec le RPN v1, car le SAN n’est pas compatible v2.&lt;/p&gt;
&lt;h3&gt;Configurer une interface privée sur la VM pour accèder au SAN&lt;/h3&gt;
&lt;p&gt;Maintenant que nous avons configuré les pré-requis dans l’interface Online, nous allons définir une interface réseau privée sur nos VM.&lt;/p&gt;
&lt;p&gt;&lt;img alt="RPN" src="/images/rpn.webp" /&gt;&lt;/p&gt;
&lt;p&gt;La passerelle nous offre une plage d’IP que nous allons pouvoir utiliser indifféremment dans Proxmox.&lt;/p&gt;
&lt;p&gt;La première étape consiste à créer ou éditer une interface privée. On en profite pour activer les jumbo frame en passant le MTU à 9000.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Création du network device" src="/images/creation_du_network_device.webp" /&gt;&lt;/p&gt;
&lt;p&gt;N’oubliez pas d’appliquer la configuration.&lt;/p&gt;
&lt;p&gt;On va ensuite créer une interface virtuelle sur la VM avec également un MTU de 9000.&lt;/p&gt;
&lt;p&gt;Au niveau IPv4 il faut choisir une adresse dans le range qui vous a été attribué avec la passerelle. La partie bridge ports doit elle reprendre d’un côté le nom de l’interface réseau source et de l’autre le VLAN id (visible dans la capture précédente avec les groupes RPN).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Création de l’interface réseau linux bridge" src="/images/creation_de_linterface_reseau_linux_bridge.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Reste à appliquer afin que les changements se reflètent dans le fichier &lt;code&gt;/etc/network/interfaces&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sur le serveur on peut vérifier la configuration de l’interface :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip a show vmbr1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On voit que l’interface réseau a bien pris l’adresse IP privée indiquée.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;11: vmbr1: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 9000 qdisc noqueue state UP group default qlen 1000
    link/ether 00:24:ec:f2:c1:fa brd ff:ff:ff:ff:ff:ff
    inet 10.88.85.4/24 scope global vmbr1
       valid_lft forever preferred_lft forever
    inet6 fe80::224:ecff:fef2:c1fa/64 scope link
       valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Toutefois dans l’interface d’Online nous devons explicitement autoriser le serveur et son adresse privée au niveau du SAN, autrement il sera impossible de d’y connecter.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Autorisation du serveur au niveau du SAN" src="/images/autorisation_du_serveur_au_niveau_du_san.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Pour arriver au résultat suivant :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Liste des serveurs et IP autorisées" src="/images/liste_des_serveurs_et_ip_autorisees.webp" /&gt;&lt;/p&gt;
&lt;h2&gt;Ajout du disque iSCSI dans Proxmox&lt;/h2&gt;
&lt;p&gt;Si on regarde où se trouve notre SAN, on constate qu’il est dans un sous réseau &lt;code&gt;10.90.x.y&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;host san-dc2-11.rpn.online.net
san-dc2-11.rpn.online.net is an alias for rpn-storage-dc2-11.rpn.online.net.
rpn-storage-dc2-11.rpn.online.net has address 10.90.254.98
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notre adresse ip privée étant en &lt;code&gt;10.88&lt;/code&gt; nous allons donc devoir lui expliquer comment accéder au SAN, pour cela nous ajoutons une route permettant d’accéder au SAN en utilisant la passerelle.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip route add 10.88.0.0/13 via 10.88.85.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sur le noeud on va ensuite installer le paquet &lt;code&gt;open-iscsi&lt;/code&gt; puis configurer le fichier &lt;code&gt;/etc/iscsi/iscsid.conf&lt;/code&gt; avec les valeurs suivantes (extraites de la &lt;a href="https://www.scaleway.com/en/docs/dedibox-network/rpn/how-to/use-rpn-san-Proxmox/"&gt;documentation&lt;/a&gt;) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node.conn[0].timeo.noop_out_interval = 0
node.conn[0].timeo.noop_out_timeout = 0
node.session.timeo.replacement_timeout = 15
node.startup = automatic
node.conn[0].iscsi.HeaderDigest = CRC32C,None
node.conn[0].iscsi.DataDigest = CRC32C,None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nous utilisons des valeurs différentes pour le &lt;code&gt;replacement_timeout&lt;/code&gt;, lié au fait que nous allons utiliser du multipath.&lt;/p&gt;
&lt;h3&gt;Configuration du multipath&lt;/h3&gt;
&lt;p&gt;Le protocole iSCSI est normalement à utiliser avec un seul hôte, c’est à dire qu’il est conçu pour être monté par une seule machine à la fois. Vous aurez d’ailleurs une alerte dans Online dès lors que vous configurerez plusieurs machines pouvant accèder au service.&lt;/p&gt;
&lt;p&gt;Le multipath permet d’accèder au disque depuis plusieurs chemins différents, ce qui offre à la fois de la redondance et une amélioration des performances.&lt;/p&gt;
&lt;p&gt;Après avoir installé le paquet &lt;code&gt;multipath-tools&lt;/code&gt; nous allons nous authentifier sur le SAN concerné depuis un hôte :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;iscsiadm -m node -T iqn.2013-01.net.online:1aokoes065of --login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On peut maintenant voir notre disque :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lsblk
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda           8:0    0     5T  0 disk
`-sda1        8:1    0     5T  0 part
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chez moi il est monté sur &lt;code&gt;/dev/sda&lt;/code&gt;. On va récupérer l’identifiant du disque :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/lib/udev/scsi_id -g -u -d /dev/sda
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;qui nous donne l’information suivante :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;149455400000000004637e3f9f5329104459d80b7be1ac5f3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Puis nous allons configurer le fichier &lt;code&gt;/etc/multipath.conf&lt;/code&gt; de la sorte :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;defaults {
 polling_interval        2
  path_selector           &amp;quot;round-robin 0&amp;quot;
  path_grouping_policy    multibus
  uid_attribute           ID_SERIAL
  rr_min_io               100
  failback                immediate
  no_path_retry           queue
 user_friendly_names     yes
}

blacklist {
  wwid .*
}

blacklist_exceptions {
 wwid &amp;quot;149455400000000004637e3f9f5329104459d80b7be1ac5f3&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;J’ai repris les réglages par défaut pour la première section. Pour la partie &lt;code&gt;blacklist&lt;/code&gt; et &lt;code&gt;blacklist_exceptions&lt;/code&gt; l’idée est de n’autoriser que ce disque en multipath et de bloquer tous les autres par défaut.&lt;/p&gt;
&lt;p&gt;On redémarre le service après l’avoir configuré.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart multipath-tools.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Puis nous allons ajouter le disque concerné au multipath.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/sbin/multipath -a 149455400000000004637e3f9f5329104459d80b7be1ac5f3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On vérifie que tout fonctionne bien.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/sbin/multipath -ll
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On voit bien notre disque :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mpatha (149455400000000004637e3f9f5329104459d80b7be1ac5f3) dm-0 IET,VIRTUAL-DISK
size=5.0T features='1 queue_if_no_path' hwhandler='0' wp=rw
`-+- policy='round-robin 0' prio=1 status=active
  `- 8:0:0:0 sda     8:0   active ready running
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Découpage du disque physique en disques logiques&lt;/h2&gt;
&lt;p&gt;On va tout d’abord créer le volume physique.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/sbin/pvcreate --metadatasize 250k -y -ff /dev/sda1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;et un volume group pour accueillir plusieurs volumes logiques :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/sbin/vgcreate storage01 /dev/sda1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On peut ensuite commencer à créer des stockages logiques au sein du volume group :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/sbin/lvcreate -L 500G -n 01 storage01
/sbin/lvcreate -L 500G -n 02 storage01
/sbin/lvcreate -L 500G -n 03 storage01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On retrouve nos volumes logiques :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/sbin/lvscan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;avec la liste suivante :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ACTIVE            '/dev/storage01/01' [500.00 GiB] inherit
ACTIVE            '/dev/storage01/02' [500.00 GiB] inherit
ACTIVE            '/dev/storage01/03' [500.00 GiB] inherit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il reste ensuite à les monter et les formatter pour les utiliser.&lt;/p&gt;
&lt;h3&gt;Ajout du stockage dans Proxmox&lt;/h3&gt;
&lt;p&gt;Proxmox gère également l’ajout d’un LVM et la création d’un volume group depuis l’interface. Toutefois je j’ai pas l’impression qu’on puisse créer des volumes logiques avec des tailles différentes.&lt;/p&gt;
&lt;p&gt;Si vous souhaitez utiliser un volume logique reprenant l’ensemble de votre disque, vous pouvez le faire dans la partie &lt;code&gt;Datacenter &amp;gt; Storage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On commence par ajouter notre stockage. On peut au besoin le limiter à certains noeuds. Dès lors que vous mettrez l’adresse du portal la target devrait être proposée, si ce n’est pas le cas je vous propose quelques commandes dans la section suivante pour débugger.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ajout du SAN dans Proxmox" src="/images/ajout_du_san_dans_proxmox.webp" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Choix de l'option iscsi dans l'interface" src="/images/choix_de_loption_iscsi_dans_linterface.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Une fois créé :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ajout du LVM" src="/images/ajout_du_lvm.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Commandes utiles&lt;/h3&gt;
&lt;p&gt;Si vous êtes bloqués dans l’interface Proxmox lorsque vous essayez d’ajouter le SAN, vous pouvez tenter une découverte des targets directement depuis le serveur :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;iscsiadm -m discovery -t sendtargets -p san-dc2-11.rpn.online.net
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nous donne le résultat suivant :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10.90.254.98:3260,1 iqn.2013-01.net.online:1aokoes065of
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Nous avons vu comment créer des volumes logiques distincts basés sur un disque SAN au travers du protocol iSCSI.&lt;/p&gt;
&lt;p&gt;Si cela fonctionne bien, gardez en tête que le fonctionnement de ces disques sera malgré tout assez (voir très) lent. Si la solution se prête bien à des fichiers dont les accès sont peu fréquents (backup par exemple) elle n'est donc pas recommandée pour des fichiers système.&lt;/p&gt;
&lt;p&gt;Notre objectif inavoué était d'ailleurs d’utiliser ces disques dans &lt;a href="https://longhorn.io/"&gt;Longhorn&lt;/a&gt;, ce qui pourra faire l'objet d'un article ultérieur.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Retour sur l'AWS Summit 2023</title>
    <id>https://martin.catty.fr/blog/retour-sur-laws-summit-2023/</id>
    <link href="https://martin.catty.fr/blog/retour-sur-laws-summit-2023/" rel="alternate"/>
    <updated>2023-04-12T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Avec plus de 100 sessions, le summit AWS offre l'opportunité à chacun de se créer son propre programme. Le nôtre était orienté autour de nos sujets de prédilections : les conteneurs, le stockage et l'orchestration.&lt;/p&gt;
&lt;h3&gt;Arrivée au palais des congrès&lt;/h3&gt;
&lt;p&gt;Pas d'hésitation possible, vu la foule on était certain d'être au bon endroit. Côté organisation les équipes étaient bien calibrées pour gérer le flux de personnes venues assister aux conférences.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pas de doute on est arrivé au Summit AWS" src="/images/pas_de_doute_on_est_arrive_au_summit_aws.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Même si les files étaient imposantes on a pu rentrer assez vite. Petite désillusion toutefois côté Grand Amphitéâtre qui faisait salle comble pour la keynote d'ouverture.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Jonathan en pleine forme pour ce summit" src="/images/jonathan_en_pleine_forme_pour_ce_summit.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Heureusement nous avons pu nous rabattre dans une autre salle proposant une retranscription en live. Bien qu'amputés de la première partie de la conférence nous avons pu retrouver quelques un de nos clients prendre place tour à tour sur scène pour vanter les mérites du passage dans le Cloud chez AWS.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pas de doute on est au bon endroit" src="/images/pas_de_doute_on_est_au_bon_endroit.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Pas de grande découverte pendant ces passages sur scène millimétrés : AWS offre une panoplie de services adaptés à tous les enjeux, qu'on soit primo entrant dans le cloud ou utilisateurs aguerris.&lt;/p&gt;
&lt;p&gt;Pour les premiers c'était l'occasion de rappeler que la dynamique de migration dans le cloud est loin d'être un processus terminé. Pour nous qui y baignons depuis plusieurs années et pour qui cela semble acquis, la présentation d'Alix Boulnois, chief digital officer d’Accor, nous offre une vision tout autre avec un taux de pénétration du cloud dans les sociétés d'hôtellerie qui avoisine les 27% seulement.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Intervenants de la keynote d'ouverture" src="/images/intervenants_de_la_keynote_douverture.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Migration de données et les différentes solutions de stockage&lt;/h3&gt;
&lt;p&gt;Première session technique avec la présentation des différentes options qui s'offre à nous lorsqu'on a besoin de stocker des données avec AWS (hint: elles sont nombreuses).&lt;/p&gt;
&lt;p&gt;On a donc parlé d'EBS et de ses différentes options possibles (gp2, gp3…).&lt;/p&gt;
&lt;p&gt;&lt;img alt="EBS" src="/images/ebs.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Mais également de EFS (service de NFS aux performances et dimensionnement élastique).&lt;/p&gt;
&lt;p&gt;Enfin nous avons eu droit à un projet mettant en scène de la recopie de données sur site vers AWS via FTP, traitement des données, création de requêtes sur mesure sur ces données et affichage de tableaux de bord.&lt;/p&gt;
&lt;p&gt;Le tout aura mobilisé pas moins de 4 services, avec une intégration assez simple : Datasync, Glue, Athena, Insights.&lt;/p&gt;
&lt;p&gt;Nous avons aussi eu droit à une présentation des coulisses &lt;a href="https://www.youtube.com/watch?v=vKSaF8NvMiQ"&gt;d'un court métrage créé par AWS, Picchu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Picchu" src="/images/picchu.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Il s'agissait de mettre en avant le fait que les studios n'ont plus besoin de maintenir des infrastructures internes lourdes et couteuses pour traiter les fichiers RAW en présentant notamment les services de liens de transfert spécifiques et file cache.&lt;/p&gt;
&lt;h3&gt;Kubernetes : observabilité des clusters EKS&lt;/h3&gt;
&lt;p&gt;Dans cette première session autour de Kubernetes nous avons eu droit à une présentation orientée observabilité.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Agenda" src="/images/agenda.webp" /&gt;&lt;/p&gt;
&lt;p&gt;L'occasion de nous rappeler la présence de tout un tas de module «blueprint» disponible pour EKS. Les blueprint représentent des intégrations tierces disponibles sur étagère, pour EKS mais pas que.&lt;/p&gt;
&lt;p&gt;On peut de fait les activer très simplement au sein de modules Terraform.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Challenges opérationnels de Kubernetes" src="/images/challenges_operationnels_de_kubernetes.webp" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="EKS Blueprint" src="/images/eks_blueprint.webp" /&gt;&lt;/p&gt;
&lt;p&gt;La session a fait un focus important sur OpenTelemetry qui devient le standard de facto pour tout ce qui a trait à l'observabilité autour des 3 piliers suivants : logs, traces et métriques.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Collection de logs avec Fluentbit" src="/images/collection_de_logs_avec_fluentbit.webp" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="OpenTelemetry" src="/images/opentelemetry.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Kubernetes : optimisez l'élasticité et les coûts de vos environnements Amazon EKS&lt;/h3&gt;
&lt;p&gt;Sans doute la conférence la plus intéressante à laquelle on ait assisté. Il s'agissait de montrer comment dimensionner et optimiser un cluster EKS avec l'outil &lt;a href="https://karpenter.sh/"&gt;Karpenter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;L'outil dans un premier temps développé de façon autonome va bientôt devenir partie prenante de l'offre Kubernetes managé d'AWS, EKS.&lt;/p&gt;
&lt;p&gt;Cette solution offre des possibilité de dimensionnement dynamique du cluster selon la charge. Vous vous demandez sans doute en quoi ce service est différent de l'autoscaling ?&lt;/p&gt;
&lt;p&gt;Il s'git ici d'offrir le meilleur ratio performance / coût. Concrètement le service permet d'utiliser plusieurs types d'instance et de rationaliser la charge.&lt;/p&gt;
&lt;p&gt;Si mon service tourne actuellement sur 2 instances Mx alors qu'il pourrait tourner sur une seule instance Tx moins chère le service ajustera automatiquement les noeuds de mon cluster.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Karpenter" src="/images/karpenter.webp" /&gt;&lt;/p&gt;
&lt;p&gt;La démo était particulièrement intéressante en montrant des montées en charge de quelques pods à plus de 6 000. L'outil offre également un instantané du coût mensuel.&lt;/p&gt;
&lt;p&gt;Plutôt que de garder quelques noeuds avec moins de 50% de charge utile, le système reschedule les pods sur 1 seul noeud pour maximiser l'efficacité. Il permet également, via configuration, l'utilisation de noeud spot permettant de faire dimuner la note.&lt;/p&gt;
&lt;p&gt;Au final l'instantané de facturation passait de +6000$ à moins de 2000. Définitivement un outil sur lequel on va se pencher sérieusement.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Démo karpenter" src="/images/demo_karpenter.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Opensearch: la recherche sémantique&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Recherche sémantique avec Opensearch" src="/images/recherche_semantique_avec_opensearch.webp" /&gt;&lt;/p&gt;
&lt;p&gt;La recherche classique par token est basée sur l'utilisation de mots-clés pour trouver des informations pertinentes. C'est le comportement par défaut d'un moteur de recherche, cependant elle ne prend pas en compte la signification ou le contexte des mots, ce qui peut entraîner des résultats de recherche imprécis ou incomplets.&lt;/p&gt;
&lt;p&gt;En revanche, la recherche sémantique avec Elasticsearch utilise des algorithmes avancés pour comprendre la signification des mots et leur contexte. Elle permet ainsi de fournir des résultats de recherche plus précis et pertinents, en prenant en compte la sémantique, les synonymes, les homonymes et les relations entre les mots.&lt;/p&gt;
&lt;p&gt;Cela a bien été illustré en prenant en exemple le cas de recherche dans un contexte e-commerce. Même si cela est acquis pour toute personne mettant en place un moteur de recherche, cette piqûre de rappel était nécessaire.&lt;/p&gt;
&lt;h3&gt;Le summit AWS ce ne sont pas que des conférences&lt;/h3&gt;
&lt;p&gt;Mais aussi l'occasion d'échanger avec les uns et les autres (on y a retroué Suse, Gitlab…) et de faire d'étonnantes découvertes au gré de nos pérégrinations comme le robot spot de Boston Dynamics en balade dans les allées.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Atlas" src="/images/atlas.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Mais également un compteur de cafés serverless.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Serverlesspresso" src="/images/serverlesspresso.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Ou encore un concours de tir au but avec un terrain bardé de capteurs.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Free kick challenge" src="/images/free_kick_challenge.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Encore une belle édition pour ce summit AWS. On en repart rechargés à bloc mais également un peu frustrés de ne pas avoir pu en voir plus. On aurait notamment aimé savoir ce qui se trame côté AI mais pour cela on attendra la publication des conférences en ligne.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Exécuter des commandes dans un conteneur avec ECS d’AWS</title>
    <id>https://martin.catty.fr/blog/executer-des-commandes-dans-un-conteneur-avec-ecs-daws/</id>
    <link href="https://martin.catty.fr/blog/executer-des-commandes-dans-un-conteneur-avec-ecs-daws/" rel="alternate"/>
    <updated>2022-06-10T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Pour les projets dont nous gérons l’infrastructure de production, nous utilisons depuis longtemps des outils tels que Rancher et Kubernetes.&lt;/p&gt;
&lt;p&gt;Avec &lt;a href="https://kubernetes.io/fr/docs/reference/kubectl/"&gt;kubectl&lt;/a&gt; on s’habitue vite au confort de pouvoir visualiser la sortie standard de ses conteneurs, exécuter des commandes à l’intérieur, &lt;em&gt;forwarder&lt;/em&gt; des ports. Je vous en parlerai un peu plus à l’occasion si vous voulez.&lt;/p&gt;
&lt;p&gt;À l’inverse lorsqu’on utilise les solutions maisons (et fermées) de fournisseurs tel qu’AWS, on se retrouve vite démunis et tributaires du bon vouloir du fournisseur pour nous fournir les outils adaptés.&lt;/p&gt;
&lt;h2&gt;Le cas ECS&lt;/h2&gt;
&lt;p&gt;ECS est le service de gestion de conteneurs d’Amazon Web Services. Il s’appuie sur le service historique EC2.&lt;/p&gt;
&lt;p&gt;Pour faire simple, vous disposez d’instances EC2 pré-configurées avec un démon Docker, ainsi que d’un orchestrateur qui vient organiser et gérer les conteneurs qui seront répartis sur ces instances.&lt;/p&gt;
&lt;p&gt;Dans ce mode de fonctionnement, vous n’avez pas la main sur l’orchestrateur mais les nœuds (instances EC2) restent visibles et potentiellement accessibles. On peut donc s’y connecter, notamment en SSH, et interagir avec les conteneurs qui s’y trouvent.&lt;/p&gt;
&lt;p&gt;Toutefois cela pose un certain nombre de problèmes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;obligation d’ouvrir le service SSH, ce qui augmente inutilement la surface d’exposition&lt;/li&gt;
&lt;li&gt;définir au préalable sur quel nœud est exécuté le conteneur avec lequel on souhaite interagir&lt;/li&gt;
&lt;li&gt;pas de possibilité d’interagir avec les conteneurs depuis la console (UI)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Le cas Fargate&lt;/h2&gt;
&lt;p&gt;Fargate pousse la logique d’ECS un cran plus loin. Cette fois-ci vous n’avez même plus accès aux ressources sous-jacentes.&lt;/p&gt;
&lt;p&gt;Évidemment il y a une infrastructure derrière, mais elle est complètement gérée pour vous, avec la promesse de ne pas avoir à gérer le dimensionnement, la sécurisation, etc.&lt;/p&gt;
&lt;p&gt;L’inconvénient par rapport à notre besoin d’interaction avec les conteneurs tient au fait que vous n’avez plus du tout de moyen de vous connecter sur les serveurs ni de rentrer dans les conteneurs par ce biais.&lt;/p&gt;
&lt;p&gt;Devant cette situation de blocage AWS a mis à jour son CLI pour proposer des commandes permettant d’interagir avec les conteneurs, quel que soit le système utilisé.&lt;/p&gt;
&lt;h2&gt;AWS CLI à la rescousse&lt;/h2&gt;
&lt;p&gt;En premier lieu vous aurez besoin du CLI AWS. Je ne vous fais pas l’affront de paraphraser &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"&gt;la documentation&lt;/a&gt; pour l’installer.&lt;/p&gt;
&lt;p&gt;Une fois réalisé vous aurez également besoin d’&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-macos"&gt;installer le plugin Session Manager&lt;/a&gt; pour le CLI.&lt;/p&gt;
&lt;p&gt;On s’assure que tout est installé correctement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fuse@archibald:~
→&lt;span class="w"&gt; &lt;/span&gt;aws&lt;span class="w"&gt; &lt;/span&gt;--version
aws-cli/2.6.3&lt;span class="w"&gt; &lt;/span&gt;Python/3.9.13&lt;span class="w"&gt; &lt;/span&gt;Darwin/21.5.0&lt;span class="w"&gt; &lt;/span&gt;source/arm64&lt;span class="w"&gt; &lt;/span&gt;prompt/off
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fuse@archibald:~
→&lt;span class="w"&gt; &lt;/span&gt;session-manager-plugin

The&lt;span class="w"&gt; &lt;/span&gt;Session&lt;span class="w"&gt; &lt;/span&gt;Manager&lt;span class="w"&gt; &lt;/span&gt;plugin&lt;span class="w"&gt; &lt;/span&gt;was&lt;span class="w"&gt; &lt;/span&gt;installed&lt;span class="w"&gt; &lt;/span&gt;successfully.&lt;span class="w"&gt; &lt;/span&gt;Use&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;AWS&lt;span class="w"&gt; &lt;/span&gt;CLI&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;session.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour permettre l’accès au conteneur, AWS utilise son service &lt;code&gt;Systems Manager&lt;/code&gt; (SSM) pour établir la connexion au conteneur et vérifier que l’utilisateur exécutant les commandes a les droits suffisants pour ce faire.&lt;/p&gt;
&lt;p&gt;Dans les faits, un agent SSM va être monté et démarré dans le conteneur pour permettre l’exécution de commandes.&lt;/p&gt;
&lt;h2&gt;Rendre l’agent SSM disponible dans les conteneurs&lt;/h2&gt;
&lt;h3&gt;Création et configuration de la politique&lt;/h3&gt;
&lt;p&gt;Pour mettre en place cet agent nous allons devoir en premier lieu créer une politique (policy) associée :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nt"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nt"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;"ssmmessages:CreateControlChannel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;"ssmmessages:CreateDataChannel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;"ssmmessages:OpenControlChannel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;"ssmmessages:OpenDataChannel"&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cette politique devra ensuite être rattachée à un rôle dont dispose l’utilisateur qui doit exécuter les commandes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Création de la police AWS" src="/images/creation_de_la_police_aws.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Utilisation du rôle par les instances&lt;/h3&gt;
&lt;p&gt;Par défaut, les conteneurs en cours d’exécution ne seront sûrement pas en capacité d’exécuter des commandes.&lt;/p&gt;
&lt;p&gt;Nous allons devoir déployer une nouvelle définition de tâche et activer l’exécution de commandes.&lt;/p&gt;
&lt;p&gt;Vous pouvez créer une définition de tâche identique à l’actuelle, simplement pour effectuer la mise à jour. En effet, si vous demandez à ECS de passer de la version N à N il ne fera rien.&lt;/p&gt;
&lt;p&gt;Il faut lui dire de passer à la N+1, même si elle est identique.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;aws&lt;span class="w"&gt; &lt;/span&gt;ecs&lt;span class="w"&gt; &lt;/span&gt;update-service&lt;span class="w"&gt; &lt;/span&gt;--cluster&lt;span class="w"&gt; &lt;/span&gt;nom_du_cluster&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--service&lt;span class="w"&gt; &lt;/span&gt;nom_du_service&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--task-definition&lt;span class="w"&gt; &lt;/span&gt;nom_de_definition_de_tache:version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--enable-execute-command
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Une fois cette commande lancée, ECS ou Fargate va rouler les conteneurs, en en démarrant de nouveaux puis en stoppant les anciens.&lt;/p&gt;
&lt;p&gt;Pour vérifier que tout fonctionne correctement on récupère un identifiant de tâche :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fuse@archibald:~
→&lt;span class="w"&gt; &lt;/span&gt;aws&lt;span class="w"&gt; &lt;/span&gt;ecs&lt;span class="w"&gt; &lt;/span&gt;list-tasks&lt;span class="w"&gt; &lt;/span&gt;--cluster&lt;span class="w"&gt; &lt;/span&gt;nom_du_cluster

&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"taskArns"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ecs:eu-west-1:034473609837:task/nom_du_cluster/63832eb11b4641729c376b95bee02f82"&lt;/span&gt;,
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ecs:eu-west-1:034473609837:task/nom_du_cluster/c9b7a91f6cdc43e0a7d6d70835b1db61"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Et on regarde si l’agent est démarré sur cette tâche :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fuse@archibald:~
→&lt;span class="w"&gt; &lt;/span&gt;aws&lt;span class="w"&gt; &lt;/span&gt;ecs&lt;span class="w"&gt; &lt;/span&gt;describe-tasks&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--tasks&lt;span class="w"&gt; &lt;/span&gt;c9b7a91f6cdc43e0a7d6d70835b1db61&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--cluster&lt;span class="w"&gt; &lt;/span&gt;nom_du_cluster&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".tasks[].containers[].managedAgents"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"lastStartedAt"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-05-31T17:24:35.002000+02:00"&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ExecuteCommandAgent"&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"lastStatus"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RUNNING"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On voit que l’agent est bien en cours d’exécution, on va pouvoir exécuter nos commandes :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fuse@archibald:~
→&lt;span class="w"&gt; &lt;/span&gt;aws&lt;span class="w"&gt; &lt;/span&gt;ecs&lt;span class="w"&gt; &lt;/span&gt;execute-command&lt;span class="w"&gt; &lt;/span&gt;--cluster&lt;span class="w"&gt; &lt;/span&gt;nom_du_cluster&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--task&lt;span class="w"&gt; &lt;/span&gt;c9b7a91f6cdc43e0a7d6d70835b1db61&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--container&lt;span class="w"&gt; &lt;/span&gt;nom_du_conteneur&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--interactive&lt;span class="w"&gt; &lt;/span&gt;--command&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bash"&lt;/span&gt;


The&lt;span class="w"&gt; &lt;/span&gt;Session&lt;span class="w"&gt; &lt;/span&gt;Manager&lt;span class="w"&gt; &lt;/span&gt;plugin&lt;span class="w"&gt; &lt;/span&gt;was&lt;span class="w"&gt; &lt;/span&gt;installed&lt;span class="w"&gt; &lt;/span&gt;successfully.&lt;span class="w"&gt; &lt;/span&gt;Use&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;AWS&lt;span class="w"&gt; &lt;/span&gt;CLI&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;session.


Starting&lt;span class="w"&gt; &lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;SessionId:&lt;span class="w"&gt; &lt;/span&gt;ecs-execute-command-0ef9e2cdabcb37a0a
root@ip-172-23-4-93:/app#
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;À vous la gloire et la prospérité, vous pouvez maintenant lancer une commande à l’intérieur de votre conteneur !&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Utiliser un registre Elastic Container Registry d'AWS avec Rancher</title>
    <id>https://martin.catty.fr/blog/utiliser-un-registre-elastic-container-registry-daws-avec-rancher/</id>
    <link href="https://martin.catty.fr/blog/utiliser-un-registre-elastic-container-registry-daws-avec-rancher/" rel="alternate"/>
    <updated>2022-06-10T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Rancher, qu'est ce que c'est ?&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.rancher.com/"&gt;Rancher&lt;/a&gt; est un excellent outil de gestion d'environnements Kubernetes. Il permet d'aggréger vos clusters on premise aussi bien que chez les cloud provider dans une unique interface.&lt;/p&gt;
&lt;p&gt;Il offre aussi la possibilité de créer des cluster Kubernetes (avec RKE ou k3s notamment) mais également d'en importer tout en ajoutant une couche d'authentification et de gestion des RBAC (Role Based Access Control).&lt;/p&gt;
&lt;p&gt;Cela permet notamment d'ouvrir des accès limités à des namespaces aux équipes de développement pour qu'elles puissent vérifier le bon fonctionnement de leurs applications.&lt;/p&gt;
&lt;p&gt;Bref, un outil que nous utilisons depuis des années (la v1 pour être précis), avant que l'intégration de Kubernetes ne remplace ce bon vieux Cattle (dont on trouve encore quelques reliques au niveau des namespaces et des agents).&lt;/p&gt;
&lt;h3&gt;Authentification des registres&lt;/h3&gt;
&lt;p&gt;Il y a par contre un point qui pêche en terme d'intégration c'est celle des registres tiers à authentification par jeton.&lt;/p&gt;
&lt;p&gt;En matière de sécurité les bonnes pratiques consistent à avoir des jetons d'authentification aux durées de vie controlées et courtes.&lt;/p&gt;
&lt;p&gt;C'est ce qu'impose notamment Amazon avec Elastic Container Registry, son service managé de stockage d'images docker.&lt;/p&gt;
&lt;p&gt;Evidemment, pour déployer des applications, nos clusters auront besoin d'y accéder au travers de Rancher qui nous propose les solutions suivantes :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interface rancher" src="/images/interface_rancher.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Comme on peut le voir on ne dispose que de solutions à base de login / mot de passe. Or, ECR impose l'usage de jetons dont la durée de validité est de 12h maximum.&lt;/p&gt;
&lt;h3&gt;Fonctionnement de l'authentification ECR&lt;/h3&gt;
&lt;p&gt;Voyons comment accéder à notre registre ECR depuis un shell, nous aurons pour cela besoin du cli AWS :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;aws&lt;span class="w"&gt; &lt;/span&gt;ecr&lt;span class="w"&gt; &lt;/span&gt;get-login-password
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cela produira une chaine de ce type, faisant office de mot de passe temporaire :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;eyJwYXlsb2FkIjoiQkhNVmppMGpCSEtWQTUxREVvUkN6cEl6QWJ4NXBHNHFPQWJKbXgvenJtNzYwMTF6
[TRUNCATED]
OiIyIiwidHlwZSI6IkRBVEFfS0VZIiwiZXhwaXJhdGlvbiI6MTY4MjAzNzkyMH0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La chaine en question pourra ensuite être utilisée pour s'authentifier sur le registre :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;login&lt;span class="w"&gt; &lt;/span&gt;--username&lt;span class="w"&gt; &lt;/span&gt;AWS&lt;span class="w"&gt; &lt;/span&gt;--password-stdin&lt;span class="w"&gt; &lt;/span&gt;ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ou en une seule commande :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;aws&lt;span class="w"&gt; &lt;/span&gt;ecr&lt;span class="w"&gt; &lt;/span&gt;get-login-password&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;login&lt;span class="w"&gt; &lt;/span&gt;--username&lt;span class="w"&gt; &lt;/span&gt;AWS&lt;span class="w"&gt; &lt;/span&gt;--password-stdin&lt;span class="w"&gt; &lt;/span&gt;ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dès lors nous pouvons accéder à notre registre de façon transparente, mais pour une durée limitée.&lt;/p&gt;
&lt;h3&gt;Intégrer un renouvellement de nos accès dans Rancher&lt;/h3&gt;
&lt;p&gt;Dans Rancher nous voulons garantir que l'accès au registre fonctionnera de manière permanente.&lt;/p&gt;
&lt;p&gt;Pour se faire nous devons donc automatiser le renouvellement du jeton.
Pour nos besoins j'ai packagé une image disponible sur le hub docker incluant à la fois &lt;code&gt;kubectl&lt;/code&gt; et le client &lt;code&gt;aws&lt;/code&gt; (fuse/aws-cli-kubectl:0.1.0).&lt;/p&gt;
&lt;p&gt;Avant de nous lancer dans la création de notre cron job nous allons renseigner en secret les valeurs nécessaires à l'authentification (&lt;code&gt;aws_account_id&lt;/code&gt;, &lt;code&gt;aws_access_key_id&lt;/code&gt;, &lt;code&gt;aws_secret_access_key&lt;/code&gt;, &lt;code&gt;aws_region&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Puis nous allons dérouler nos commandes dans un pod, qui utilise notre image et injecte notre configuration. Plutôt que de procéder par essai / erreur, on lance une image qui ne fait rien (&lt;code&gt;sleep 3600&lt;/code&gt;) à laquelle on s'attache pour éxécuter nos commandes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Pod&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ecr-token-renew&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ecr-token-renew&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fuse/aws-cli-kubectl:0.1.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sleep"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3600"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-ecr&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws_access_key_id&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS_REGION&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-ecr&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws_region&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-ecr&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws_secret_access_key&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;restartPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;OnFailure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notre but est de supprimer et de recréer à intervalle régulier le secret contenant les informations de connexion au registre docker.&lt;/p&gt;
&lt;p&gt;Notre pod (et notre cron job) étant lancé avec un compte de service (&lt;code&gt;serviceaccount&lt;/code&gt;), nous allons vérifier que nous avons les bons droits.&lt;/p&gt;
&lt;p&gt;On commence par rentre dans notre conteneur :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;ecr-token-renew&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;/bin/sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;puis on teste les accès :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;auth&lt;span class="w"&gt; &lt;/span&gt;can-i&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;secrets
no
$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;auth&lt;span class="w"&gt; &lt;/span&gt;can-i&lt;span class="w"&gt; &lt;/span&gt;delete&lt;span class="w"&gt; &lt;/span&gt;secrets
no
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;De base notre &lt;code&gt;serviceaccount&lt;/code&gt; ne dispose pas des droits suffisants. Nous allons donc créer un rôle permettant de réaliser les opérations de type CRUD sur les secrets et lui attacher.&lt;/p&gt;
&lt;p&gt;On crée donc le fichier &lt;code&gt;role.yml&lt;/code&gt; comme suit :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Role&lt;/span&gt;
&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;app-namespace&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;crud-secrets&lt;/span&gt;
&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"secrets"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"get"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"list"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"watch"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"create"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"update"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"patch"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Et on l'applique :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;role.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;De même on crée l'objet &lt;code&gt;RoleBinding&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;RoleBinding&lt;/span&gt;
&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;crud-secrets-binding&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;app-namespace&lt;/span&gt;
&lt;span class="nt"&gt;subjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;default&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;app-namespace&lt;/span&gt;
&lt;span class="nt"&gt;roleRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Role&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;crud-secrets&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;apiGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Et on l'applique :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;role.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On peut de nouveau vérifier que tout fonctionne avec &lt;code&gt;kubectl auth can-i&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Suppression et ajout du secret à intervalle régulier&lt;/h3&gt;
&lt;p&gt;Maintenant que nous avons les bons droits nous allons dérouler les commandes suivantes :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# le nom de notre secret, à votre bon vouloir&lt;/span&gt;
&lt;span class="nv"&gt;SECRET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ecr-registry-token
&lt;span class="c1"&gt;# on récupère le token en s'authentifiant (le cli utilise automatiquement AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY)&lt;/span&gt;
&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;aws&lt;span class="w"&gt; &lt;/span&gt;ecr&lt;span class="w"&gt; &lt;/span&gt;get-login-password&lt;span class="w"&gt; &lt;/span&gt;--region&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# on supprime le secret sans râler s'il n'existe pas encore (premier run)&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;delete&lt;span class="w"&gt; &lt;/span&gt;secret&lt;span class="w"&gt; &lt;/span&gt;--ignore-not-found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SECRET_NAME&lt;/span&gt;
&lt;span class="c1"&gt;# on recrée le secret en injectant la configuration qui va bien&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;secret&lt;span class="w"&gt; &lt;/span&gt;docker-registry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SECRET_NAME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--docker-server&lt;span class="o"&gt;=&lt;/span&gt;https://&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.dkr.ecr.&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.amazonaws.com&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--docker-username&lt;span class="o"&gt;=&lt;/span&gt;AWS&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
--docker-password&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Une fois qu'on a vérifié que tout fonctionnait correctement de manière interactive il nous reste à packager le tout dans un &lt;code&gt;CronJob&lt;/code&gt; qui tournera toutes les 6h (on évite de le faire tourner toutes les 12h pour ne pas être trop juste et se garder de la latitude si une exécution échoue).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;batch/v1beta1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;CronJob&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ecr-token-renew&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*/6&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;jobTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ecr-token-renew&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fuse/aws-cli-kubectl:0.1.0&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/bin/sh&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;-c&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|-&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="no"&gt;SECRET_NAME=ecr-registry-token&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="no"&gt;TOKEN=$(aws ecr get-login-password --region $AWS_REGION)&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="no"&gt;kubectl delete secret --ignore-not-found $SECRET_NAME&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="no"&gt;kubectl create secret docker-registry $SECRET_NAME \&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="no"&gt;--docker-server=https://${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com \&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="no"&gt;--docker-username=AWS \&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="no"&gt;--docker-password="${TOKEN}"&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS_ACCOUNT_ID&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-ecr&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws_account_id&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-ecr&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws_access_key_id&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS_REGION&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-ecr&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws_region&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws-ecr&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;aws_secret_access_key&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;restartPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;OnFailure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Dans cet article nous avons vu comment palier au manque d'intégration dynamique des registres dans Rancher en générant nous même des tokens à intervalle régulier.&lt;/p&gt;
&lt;p&gt;J'espère qu'il vous sera utile en attendant une solution built in.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>S'authentifier avec un facteur tiers en utilisant l'API WebAuthn</title>
    <id>https://martin.catty.fr/blog/sauthentifier-avec-un-facteur-tiers-en-utilisant-lapi-webauthn/</id>
    <link href="https://martin.catty.fr/blog/sauthentifier-avec-un-facteur-tiers-en-utilisant-lapi-webauthn/" rel="alternate"/>
    <updated>2021-09-10T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;La méthode d'authentification la plus courante sur le web n'a pas changée depuis des années, elle nécessite un login et un mot de passe.&lt;/p&gt;
&lt;p&gt;Si les applications web se doivent de respecter &lt;a href="https://www.synbioz.com/blog/tech/les-enjeux-de-la-securite-des-applications-web"&gt;certains critères de sécurité&lt;/a&gt;, il en va de même pour celui ou celle qui se situe entre le clavier et la chaise.&lt;/p&gt;
&lt;p&gt;Si vous êtes un utilisateur aguerri, il y a des chances que vous utilisiez un gestionnaire de mot de passe tel que 1Password, Bitwarden ou LastPass.&lt;/p&gt;
&lt;p&gt;Dans le cas contraire il y a de fortes chances que vous utilisiez un même mot de passe sur différents services, aussi sensibles soient-ils.&lt;/p&gt;
&lt;p&gt;En sachant que tous les sites / applications proposent une fonctionnalité de type «Mot de passe oublié» je vous laisse imaginer les dégâts si quelqu'un venait à accéder à votre boite mail.&lt;/p&gt;
&lt;h3&gt;Comment faire mieux que le bon vieux couple login / mot de passe ?&lt;/h3&gt;
&lt;p&gt;Progressivement, des solutions ont été imaginées pour renforcer la sécurité pour les utilisateurs finaux sans que celles-ci ne soient trop compliquées à utiliser.&lt;/p&gt;
&lt;p&gt;On a vu l'avènement notamment de solutions 2FA ou MFA. La première désigne l'authentification à 2 facteurs, la seconde en multi facteurs (au moins 2).&lt;/p&gt;
&lt;p&gt;Pour accéder au service concerné il faudra donc en plus du mot de passe un complément, comme un mot de passe temporaire reçu par SMS ou un jeton.&lt;/p&gt;
&lt;p&gt;Les gestionnaires comme 1Password savent gérer ces jetons (OTP).&lt;/p&gt;
&lt;p&gt;&lt;img alt="OTP 1Password" src="/images/otp_1password.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Mais cela décale finalement le problème quand vous utilisez le même outil pour stocker à la fois le mot de passe et le générateur de jetons. En effet votre coffre est lui-même protégé par un… mot de passe.&lt;/p&gt;
&lt;p&gt;Quelqu'un qui disposerait de votre mot de passe pour un service donné ne pourrait pas s'y connecter car incapable de produire un facteur d'authentification supplémentaire.&lt;/p&gt;
&lt;p&gt;Si cet usurpateur dispose par contre du mot de passe de votre coffre il accédera à l'ensemble de vos comptes, encore plus si votre gestionnaire est en charge des OTP.&lt;/p&gt;
&lt;h3&gt;S'appuyer sur ce qui marche : le chiffrement asymétrique&lt;/h3&gt;
&lt;p&gt;On a plusieurs exemples d'outils qui s'appuient sur un chiffrement asymétrique : GPG ou SSH pour n'en citer que deux.&lt;/p&gt;
&lt;p&gt;La logique est la suivante dans le cas de GPG : un message est encodé au moyen d'une clé (publique) et décodé au moyen d'une clé privée.&lt;/p&gt;
&lt;p&gt;Si je veux envoyer un courriel à Tom, j'utilise sa clé publique et il pourra le déchiffrer au moyen de sa clé privée. Toute personne interceptant le message en cours de route ne pourra y voir qu'une suite de caractères incompréhensibles.&lt;/p&gt;
&lt;p&gt;L'algorithme étant non réversible il n'est donc pas possible de deviner la partie privée depuis la partie publique.&lt;/p&gt;
&lt;h3&gt;Introduire ce mécanisme sur le web&lt;/h3&gt;
&lt;p&gt;Le W3C propose une spécification permettant de porter et utiliser ce mécanisme dans nos navigateurs : &lt;a href="https://w3c.github.io/webauthn/"&gt;WebAuthn&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Elle a beau être en draft, &lt;a href="https://www.caniuse.com/webauthn"&gt;le support des navigateurs est excellent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;WebAuthn s'appuie sur une architecture à 3 parties :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le service auquel on souhaite accéder. Dans le jargon on appelle ça une &lt;em&gt;relying party&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;le client accédant au service (on ne parle pas ici de l'utilisateur final mais du système qui va consommer le service, comme un navigateur par exemple)&lt;/li&gt;
&lt;li&gt;un mécanisme d'authentification déporté (une clé USB, un capteur Touch ID, un smartphone…). On parle ici d'&lt;em&gt;authenticator&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les mécanismes d'authentification déportés peuvent être multiples. Un service pourrait vous demander plusieurs mécanismes tiers pour y accéder mais la plupart du temps on peut en enregistrer plusieurs et utiliser l'un de ceux enregistrés.&lt;/p&gt;
&lt;p&gt;Personnellement j'utilise une clé Yubikey comme système tiers, mais certains services proposent également Touch ID par exemple.&lt;/p&gt;
&lt;p&gt;L'intérêt de ce mécanisme d'authentification est de gérer pour vous les paires « clé publique / clé privée » de façon transparente. Dans le cas d'une clé c'est elle qui est en charge du stockage sécurisé. Touch ID fonctionne quant à lui grâce à une enclave sécurisée qui dispose de son propre logiciel dédié, il fonctionne en dehors du système d'exploitation d'Apple.&lt;/p&gt;
&lt;p&gt;On peut voir un exemple d'usage sur le site de &lt;a href="https://demo.yubico.com/webauthn/"&gt;démo de yubico&lt;/a&gt;. Après avoir enregistré le ou les mécanismes tiers dans son compte on peut s'en servir lors de la connexion.&lt;/p&gt;
&lt;p&gt;Ici avec le capteur Touch ID :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Authentification avec Touch ID" src="/images/authentification_avec_touch_id.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Puis avec la clé Yubikey (après l'avoir enregistrée) :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Authentification avec Yubikey" src="/images/authentification_avec_yubikey.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Comment ça marche ?&lt;/h3&gt;
&lt;p&gt;Le service qui souhaite permettre une authentification avec WebAuthn doit dans un premier temps permettre à l'utilisateur disposant d'un compte d'enregistrer ce mécanisme, afin qu'il soit lié à son compte utilisateur.&lt;/p&gt;
&lt;p&gt;Il va l'indiquer au client (navigateur) qui va être en charge de demander au mécanisme d'authentification (authenticator) la génération d'un nouveau couple de clé publique / privée.&lt;/p&gt;
&lt;p&gt;Si tout se passe bien, la clé publique sera transmise jusqu'au service qui l'associera au compte.&lt;/p&gt;
&lt;p&gt;Pour l'authentification au service, le client transférera les informations au mécanisme d'authentification en précisant de qui vient la requête.&lt;/p&gt;
&lt;p&gt;Le mécanisme d'authentification vérifiera la présence de l'utilisateur, par exemple en lui demandant d'appuyer sur le bouton dans le cas d'une clé Yubikey ou en faisant une vérification biométrique dans le cas de Touch ID.&lt;/p&gt;
&lt;p&gt;Une fois la vérification effectuée, le mécanisme d'authentification signera les données utiles à l'aide de la clé privée, identifiée par l'identifiant envoyé par le service, et renverra l'assertion au client.&lt;/p&gt;
&lt;p&gt;Le client jouera une nouvelle fois le rôle de passe-plat vers le service qui vérifiera la signature obtenue en réponse au défi.&lt;/p&gt;
&lt;p&gt;Pour faciliter l'implémentation, les navigateurs proposent des API permettant de gérer la génération de ces couples de clés et plus généralement des mots de passe.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PasswordCredential&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fuse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hardc0re"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ouvrira cette boite de dialogue bien connue :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Modale d'enregistrement du mot de passe" src="/images/modale_denregistrement_du_mot_de_passe.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Le code suivant permet lui de simuler la création d'un défi (challenge) permettant la création d'une clé publique.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// on crée une chaine de caractères aléatoires matérialisant le défi&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// l'identifiant ici viendrait du service, il ne contient pas&lt;/span&gt;
&lt;span class="c1"&gt;// d'information personnelle mais permettra au service de faire le lien&lt;/span&gt;
&lt;span class="c1"&gt;// entre cet identifiant unique et le compte utilisateur concerné&lt;/span&gt;
&lt;span class="c1"&gt;// Ici j'ai utilisé un mécanisme très robuste à base d'encodage en base64 :)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bWNhdHR5QHN5bmJpb3ouY29t"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;rp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mon super service."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"www.synbioz.com"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcatty@synbioz.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Martin Catty"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// pubKeyCredParams permet de spécifier les mécanismes de chiffrement&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// autorisés. -7 pour SHA-256, -37 pour RSA, -257 pour RS256.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;pubKeyCredParams&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alg&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alg&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;37&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ce qui nous donne en retour :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;PublicKeyCredential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rawid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AuthenticatorAttestation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GhXH3mKw7ayi-my Vorz LUOTDOEE6FqRc7NWCqRGENDgvDm-JpwP900E60YVDABRgyXwyptoMQfiPc9qc@yzuo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-key"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GhXH3mKw7ayi-myVQrz LUẬTDOEE6FqRc7NWCqRGENDgvDm-JpwPgQQE6 ØYVDABRgykXwyptoMQfipc9qc@yZUQ"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;rawId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AuthenticatorAttestationResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;attestationObject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;226&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clientDataJSON&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;139&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-key"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nx"&gt;Prototype&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PublicKeyCredential&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le client va transférer «l'attestation» au service qui sera en charge de vérifier son contenu (qui est chiffré) et si tout est OK il enregistrera la clé publique associée à l'utilisateur concerné.&lt;/p&gt;
&lt;p&gt;La prochaine fois, pour permettre l'authentification de l'utilisateur concerné, le service fournira des identifiants de mécanisme d'authentification autorisés (comme vu plus haut il peut y en avoir plusieurs) et lancera un nouveau défi.&lt;/p&gt;
&lt;p&gt;Le processus derrière sera globalement le même que celui de l'enregistrement du mécanisme d'authentification.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// l'identifiant de l'attestation récupéré à la création de la clé publique&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;credential_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"kt-fb-x1WsKgTe2TW8tOtkIerPhIOVormIM_PXoAc70Uvdb92Vg8UIv3gg_Crvn6WfIQnNNB6gSr-rnpiLUiuw"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;public_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;allowCredentials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;public_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;On le voit, les API à disposition sont matures et permettent déjà d'utiliser des mécanismes 2FA.&lt;/p&gt;
&lt;p&gt;De plus en plus de sites ou applications le proposent et il est temps de le généraliser, notamment car la législation en place devient de plus en plus contraignante (on peut citer par exemple la &lt;a href="https://www.certeurope.fr/blog/dsp2-et-authentification-forte-que-prevoit-la-directive-europeenne/"&gt;DSP2&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Dans un prochain article nous discuterons de la possibilité d'utiliser des identifiants déportés sur un système tiers via l'API &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FederatedCredential"&gt;FederatedCredentials&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Productivité : outils et méthodes</title>
    <id>https://martin.catty.fr/blog/productivite-outils-et-methodes/</id>
    <link href="https://martin.catty.fr/blog/productivite-outils-et-methodes/" rel="alternate"/>
    <updated>2021-06-11T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Je voudrais aujourd'hui vous parler de productivité, mais aussi et surtout de mon retour d'expérience sur les outils et méthodes qui m'aident à obtenir un bon niveau d'efficience.&lt;/p&gt;
&lt;p&gt;On parlera ici plutôt d'efficience dans la mesure où cela ne se limite pas à la seule atteinte de résultats (efficacité) mais aussi et surtout des moyens / efforts à apporter pour les atteindre.&lt;/p&gt;
&lt;h3&gt;Qu'est-ce qu'on appelle productivité ?&lt;/h3&gt;
&lt;p&gt;Si on nomme X la production en sortie et Y l'effort en entrée ; je dirais que pour être plus productif on augmente X sans toucher à Y ; pour être plus efficient on diminue Y sans toucher à X.&lt;/p&gt;
&lt;p&gt;Dans cet article, mon approche de la productivité est plutôt orientée bien-être / confort / limiter les répétitions, plutôt que compétitivité. Il n'en reste pas moins qu'améliorer la productivité accroît de facto cette dernière.&lt;/p&gt;
&lt;h3&gt;Poser le diagnostic&lt;/h3&gt;
&lt;p&gt;Il n'est pas évident de prendre du recul sur son quotidien pour définir ce qu'on peut améliorer.&lt;/p&gt;
&lt;p&gt;La première règle, c'est donc de prendre le temps nécessaire pour analyser ce qui nous occupe, ce qu'on a tendance à faire de manière répétitive et s'il existe un moyen d'y consacrer moins d'effort.&lt;/p&gt;
&lt;h3&gt;Améliorer son environnement&lt;/h3&gt;
&lt;h4&gt;L'environnement global&lt;/h4&gt;
&lt;p&gt;On n'a pas toujours la main pour prendre les décisions relatives à son environnement de travail, pour autant, c'est un critère fondamental sur lequel il ne faut pas transiger.&lt;/p&gt;
&lt;p&gt;Je suis toujours effaré d'avoir en communication des personnes qui passent 8 h par jour sur un matériel misérable. C'est le comble du contreproductif. Est-ce qu'on imaginerait demander à des livreurs d'utiliser des carrioles à chevaux ?&lt;/p&gt;
&lt;p&gt;C'est vrai aussi bien pour les machines que pour les équipements associés : micro de bonne qualité, caméra, connexion internet satisfaisante, outil de visio adapté.&lt;/p&gt;
&lt;p&gt;Si on mettait bout à bout le nombre de minutes perdues en visio à base de « ma caméra ne fonctionne pas, je n'ai pas de son, tu m'entends ?, ça saccade » je pense qu'on doit facilement perdre à l'échelle nationale, plusieurs années de productivité chaque jour.&lt;/p&gt;
&lt;p&gt;Si vous êtes décideur, ne lésinez pas sur la mise à disposition d'un bon environnement de travail.&lt;/p&gt;
&lt;h4&gt;L'environnement personnel&lt;/h4&gt;
&lt;p&gt;En termes d'environnement, chacun ses préférences personnelles. Un investissement de productivité que je n'ai jamais regretté c'est le passage à la disposition de clavier « bépo », en 2011.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Clavier Typematrix" src="/images/clavier_typematrix.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Elle s'est accompagnée d'un achat de clavier Typematrix, qui m'a accompagné 8 ans avant de laisser la place à un Ergodox.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Clavier Ergodox" src="/images/clavier_ergodox.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Alors oui c'est compliqué de changer de disposition quand on s'y est habitué depuis des années, mais honnêtement le jeu en vaut la chandelle.&lt;/p&gt;
&lt;p&gt;Vitesse de frappe, précision, moins de fatigue musculaire, le calcul est vite fait.&lt;/p&gt;
&lt;p&gt;Pourquoi remiser le Typematrix au placard ? L'Ergodox offre l'avantage d'être splitté (ce qui permet de garder les bras dans la continuité des épaules plutôt que tout serrés) et il est complètement programmable.&lt;/p&gt;
&lt;p&gt;J'ai donc pu modifier le &lt;em&gt;layout&lt;/em&gt; de base et notamment les quelques touches particulières au niveau des pouces pour avoir quelque chose de confortable et qui me semble naturel à l'usage. J'utilise ces touches par exemple pour changer de musique, mettre en pause, etc.&lt;/p&gt;
&lt;p&gt;Par ailleurs l'Ergodox offre la possibilité de gérer plusieurs &lt;em&gt;layouts&lt;/em&gt;, en mode permanent ou temporaire.&lt;/p&gt;
&lt;p&gt;Concrètement on peut donc appuyer ou maintenir une touche pour changer de &lt;em&gt;layout&lt;/em&gt; de façon durable ou simplement tant qu'on maintient la touche.&lt;/p&gt;
&lt;p&gt;Je m'en sers par exemple en mode temporaire pour avoir les chiffres sous la main. Tant que je maintiens la touche tout en bas à gauche du clavier, les touches du milieu de la partie droite du clavier se transforme en pad numérique.&lt;/p&gt;
&lt;p&gt;L'Ergodox offre par contre l'inconvénient (en collectivité) d'être mécanique et donc bruyant. J'ai donc conservé mon Typematrix au bureau.&lt;/p&gt;
&lt;p&gt;Bref, il ne s'agit pas de vous vendre ce produit en particulier, mais d'attirer votre attention sur l'importance que revêt le clavier et la disposition des touches. Votre canal carpien pourrait vous dire merci dans quelques années.&lt;/p&gt;
&lt;p&gt;Pour commencer, vous pouvez utiliser des outils de mesure de votre vitesse de frappe ainsi que de votre précision, pour situer votre niveau d'efficience avec votre clavier.&lt;/p&gt;
&lt;h3&gt;Utiliser les bons outils&lt;/h3&gt;
&lt;h4&gt;Dictée vocale&lt;/h4&gt;
&lt;p&gt;Au quotidien, je suis amené à rédiger énormément (ticket, email, document, contrat…). Avoir un bon clavier aide bien sûr, mais on dispose parfois de plus efficace encore : la dictée vocale.&lt;/p&gt;
&lt;p&gt;Sous Mac OS X je la trouve assez réussie. Je lui ai affecté un raccourci clavier qui me permet depuis n'importe quel champ texte de venir dicter mon contenu.&lt;/p&gt;
&lt;p&gt;Évidemment il se perd un peu sur les acronymes, nombreux, de notre métier, mais globalement cela me fait tout de même gagner un temps considérable.&lt;/p&gt;
&lt;p&gt;À éviter quand on est dans le même bureau avec ses collègues, mais je l'affectionne &lt;a href="https://www.synbioz.com/blog/tech/pourquoi-nous-avons-tres-vite-adopte-le-teletravail"&gt;tout particulièrement en télétravail&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Activer la dictée vocale" src="/images/activer_la_dictee_vocale.webp" /&gt;&lt;/p&gt;
&lt;h4&gt;Ré-organiser les fenêtres avec Spectacle&lt;/h4&gt;
&lt;p&gt;Un outil que j'utilise au moins 100 fois par jour c'est &lt;a href="https://www.spectacleapp.com/"&gt;spectacle&lt;/a&gt;. Même si l'outil n'est plus maintenu je continue de m'en servir.&lt;/p&gt;
&lt;p&gt;Il permet de déplacer et redimensionner ses fenêtres avec un raccourci clavier.
Je peux facilement mettre deux fenêtres côte à côté, basculer une fenêtre sur un autre écran, la centrer sur l'écran, passer en mode plein écran…&lt;/p&gt;
&lt;p&gt;&lt;img alt="Spectacle" src="/images/spectacle.webp" /&gt;&lt;/p&gt;
&lt;h4&gt;Extraire un onglet en tant que fenêtre&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://chrome.google.com/webstore/detail/tab-to-windowpopup-keyboa/adbkphmimfcaeonicpmamfddbbnphikh"&gt;Tab to Window/Popup&lt;/a&gt; est une extension que j'utilise tout le temps pour transformer un onglet en fenêtre.&lt;/p&gt;
&lt;p&gt;Elle offre l'avantage de ré-organiser intelligemment les fenêtres pour que la nouvelle soit visible à côté de l'ancienne notamment.&lt;/p&gt;
&lt;p&gt;En attendant que Brave le supporte nativement, l'extension fait son office.&lt;/p&gt;
&lt;h4&gt;Naviguer dans les pages et les onglets de son navigateur&lt;/h4&gt;
&lt;p&gt;J'utilise pour cela &lt;a href="https://chrome.google.com/webstore/detail/vimium/dbepggeogbaibhgnhhndojpepiihcmeb"&gt;Vimium&lt;/a&gt;. Comme son nom l'indique elle s'inspire de Vim.&lt;/p&gt;
&lt;p&gt;Si comme moi vous commencez la semaine plein de bonnes résolutions quant à votre gestion d'onglets et que vous finissez immanquablement avec 128 onglets dès le lundi, le mode recherche d'onglet est particulièrement utile.&lt;/p&gt;
&lt;p&gt;Il dispose d'un lanceur type Alfred / Spotlight pour rechercher dans ses onglets.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Recherche dans les onglets" src="/images/recherche_dans_les_onglets.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Elle permet également de trouver et accéder à un élément (lien, champ) de la page sans utiliser sa souris / trackpad.&lt;/p&gt;
&lt;p&gt;En tapant &lt;code&gt;f&lt;/code&gt; on dispose de raccourcis permettant d'accéder à l'élément surligné. En tapant &lt;code&gt;r&lt;/code&gt; on recharge la page.&lt;/p&gt;
&lt;p&gt;Le dernier usage que j'utilise régulièrement c'est la possibilité de rechercher dans la page avec simplement &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Alfred : le spotlight amélioré&lt;/h4&gt;
&lt;p&gt;S'il ne fallait en garder qu'un ce serait celui-là. Alfred permet de faire tout un tas de choses, regardons-y de plus près.&lt;/p&gt;
&lt;h4&gt;Chercher&lt;/h4&gt;
&lt;p&gt;De base, dès qu'on tape quelque chose dans l'invite qui ne correspond pas à un &lt;em&gt;workflow&lt;/em&gt; ou un mot clé réservé, Alfred va nous permettre de le chercher, soit en local (en tant que fichier), soit dans un moteur de recherche.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Recherche locale" src="/images/recherche_locale.webp" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Recherche dans un moteur de recherche" src="/images/recherche_dans_un_moteur_de_recherche.webp" /&gt;&lt;/p&gt;
&lt;h4&gt;Historique du presse-papier&lt;/h4&gt;
&lt;p&gt;Une « killer feature » d'Alfred, qui offre un presse-papier amélioré qui enregistre chacun des « copier » et les conserve pendant un temps que l'on peut configurer.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Historique de presse-papier" src="/images/historique_de_presse_papier.webp" /&gt;&lt;/p&gt;
&lt;p&gt;On peut bien sûr rechercher dans cet historique.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Recherche dans l'historique d'Alfred" src="/images/recherche_dans_lhistorique_dalfred.webp" /&gt;&lt;/p&gt;
&lt;h4&gt;Snippet &amp;amp; expansion automatique&lt;/h4&gt;
&lt;p&gt;Je n'ai aucune aptitude particulière à retenir de longues séries alphanumérique.&lt;/p&gt;
&lt;p&gt;Les &lt;em&gt;snippets&lt;/em&gt; sont très utiles pour ça, j'y enregistre adresses, numéro de téléphone, numéro de TVA, clé publique GPG…&lt;/p&gt;
&lt;p&gt;On peut également utiliser des &lt;em&gt;snippets&lt;/em&gt; dynamiques, par exemple en se basant sur la date du jour.&lt;/p&gt;
&lt;p&gt;Il est également possible de repositionner le curseur :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Snippet de code" src="/images/snippet_de_code.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Cela va créer un bloc de code utilisable dans du &lt;em&gt;markup&lt;/em&gt; et repositionner le curseur entre les délimiteurs.&lt;/p&gt;
&lt;p&gt;Ces éléments peuvent se déclencher en lançant une commande spécifique mais également grâce à un système d'auto-expansion.&lt;/p&gt;
&lt;p&gt;Comme on le voit dans ma capture d'écran, si je tape &lt;code&gt;acode&lt;/code&gt;, cela va déclencher le &lt;em&gt;snippet&lt;/em&gt;, où que je sois.&lt;/p&gt;
&lt;h4&gt;Commandes systèmes&lt;/h4&gt;
&lt;p&gt;On dispose également de raccourcis pour des commandes systèmes, ici si vous voulez vous déconnecter du système :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Commande système" src="/images/commande_systeme.webp" /&gt;&lt;/p&gt;
&lt;h4&gt;Workflow&lt;/h4&gt;
&lt;p&gt;En enrichissant l'outil de &lt;em&gt;workflow&lt;/em&gt; on peut réaliser des actions plus abouties, qui enchainent plusieurs étapes, prennent des paramètres en entrée, etc.&lt;/p&gt;
&lt;p&gt;Par exemple pour rechercher dans la documentation de Ruby on Rails :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Chercher dans la documentation rails" src="/images/chercher_dans_la_documentation_rails.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Le &lt;em&gt;workflow&lt;/em&gt; que j'utilise le plus c'est sans aucun doute celui de GitLab.&lt;/p&gt;
&lt;p&gt;Avec le raccourci &lt;code&gt;gl&lt;/code&gt; je peux dynamiquement sélectionner un projet après avoir tapé quelques caractères :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Choisir un projet" src="/images/choisir_un_projet.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Puis choisir une action associée :&lt;/p&gt;
&lt;p&gt;&lt;img alt="Choisir une action" src="/images/choisir_une_action.webp" /&gt;&lt;/p&gt;
&lt;p&gt;À noter qu'on peut très facilement modifier les workflow, l'action &lt;code&gt;New issue&lt;/code&gt; n'était pas là de base.&lt;/p&gt;
&lt;h3&gt;Créer ses outils&lt;/h3&gt;
&lt;p&gt;Quand on ne trouve pas son compte il faut faire ou faire faire ses propres outils. C'est d'ailleurs notre métier que de créer des outils qui facilitent le quotidien de nos clients.&lt;/p&gt;
&lt;h4&gt;Script pour changer rapidement les labels sur GitLab&lt;/h4&gt;
&lt;p&gt;Alors évidemment parfois on le fait également pour nous même. C'est le cas récemment avec l'écriture d'un &lt;a href="https://www.synbioz.com/en/blog/tech/gitlab-userscript-to-ease-label-transitions"&gt;script permettant de faciliter le passage d'un label à un autre&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;pl-gitlab&lt;/h4&gt;
&lt;p&gt;pl-gitlab est un autre exemple d'outil maison, de type CLI, qui s'appuie sur l'API de GitLab et que nous comptons opensourcer.&lt;/p&gt;
&lt;p&gt;Il nous permet actuellement de :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;créer les X prochaines milestones pour un groupe donné&lt;/li&gt;
&lt;li&gt;créer des &lt;em&gt;boards&lt;/em&gt; pour chaque &lt;em&gt;milestone&lt;/em&gt; avec le paramétrage automatique des colonnes&lt;/li&gt;
&lt;li&gt;exporter et importer les permissions sur base d'un fichier YAML qu'on maintient à jour. Concrètement à l'arrivée d'un collaborateur il nous suffit de l'ajouter dans l'équipe, d'appliquer, et il aura accès à tous les dépôts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mais également tout un tas d'autres choses qui nous évitent une gestion manuelle et fastidieuse.&lt;/p&gt;
&lt;p&gt;&lt;img alt="pl-gitlab" src="/images/pl_gitlab.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;J'espère que cet article vous aura appris 2-3 choses. Comme on aime raffiner nos outils et nos méthodes n'hésitez pas à nous dire également quelles sont vos astuces et si vous êtes intéressés par l'ouverture de nos outils sur &lt;a href="https://twitter.com/synbioz"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>GitLab userscript to ease label transitions</title>
    <id>https://martin.catty.fr/en/blog/gitlab-userscript-to-ease-label-transitions/</id>
    <link href="https://martin.catty.fr/en/blog/gitlab-userscript-to-ease-label-transitions/" rel="alternate"/>
    <updated>2021-05-14T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;GitLab&lt;/h3&gt;
&lt;p&gt;For the past years we, at Synbioz, have been heavy users of GitLab. It progressively became our central tool for day to day job.&lt;/p&gt;
&lt;p&gt;This choice has mostly been driven by its devops capabilities.&lt;/p&gt;
&lt;p&gt;Otherwise, I have still found its UX quite painful when it comes to change a label on a given issue.&lt;/p&gt;
&lt;h3&gt;What's wrong with label change on GitLab ?&lt;/h3&gt;
&lt;p&gt;We have a common workflow based on boards, moving an issue to the right or to the left. Original, isn't it ?&lt;/p&gt;
&lt;p&gt;We also have a few automations to help us create boards, milestones etc… automatically ; even define permissions on the whole GitLab, but that's another story.&lt;/p&gt;
&lt;p&gt;For now, here is what our boards headers look like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="board part 1" src="https://narcisse.synbioz.com/attachments/8f9e4e10-b39e-446a-ab8d-dc14e4d9a44e/variants/1461aec3-64f4-4f91-9c8d-1c0244eb0e14/src" /&gt;
&lt;img alt="board part 2" src="https://narcisse.synbioz.com/attachments/5dac3400-bba3-42aa-9740-567895112420/variants/863db6a3-170e-4a5a-9c89-3c0b4fca8c85/src" /&gt;&lt;/p&gt;
&lt;p&gt;Sure we can easily drag'n'drop the issue from a column to an other.&lt;/p&gt;
&lt;p&gt;When we are doing some testing we can’t satisfied with the issue’s title only.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Issue details" src="https://narcisse.synbioz.com/attachments/1640bbeb-4033-4bf8-9c90-243031171417/variants/350d13b0-919b-4da5-8408-df8041ea1500/src" /&gt;&lt;/p&gt;
&lt;p&gt;So let's consider this issue is OK. I will have to remove the current label &lt;code&gt;to validate::functionally&lt;/code&gt; and put another one: &lt;code&gt;to validate::technically&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To do so I have to go through no less than 6 (!) steps :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;click edit&lt;/li&gt;
&lt;li&gt;type a few characters&lt;/li&gt;
&lt;li&gt;scroll to the wanted label or use the arrows&lt;/li&gt;
&lt;li&gt;click or hit enter to select it&lt;/li&gt;
&lt;li&gt;do not forget to «exit» the dropdown to apply the label (you can't use &lt;code&gt;esc&lt;/code&gt; key)&lt;/li&gt;
&lt;li&gt;remove the old label (this one is easy, just click on the cross)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is far from ideal from my point of view. When you process a lot of issues this is a massive waste of time and error-prone.&lt;/p&gt;
&lt;h3&gt;Label proposal&lt;/h3&gt;
&lt;p&gt;Considering there is always a human decision behind label changes, we can't automatically apply a label based on another one removal or addition.&lt;/p&gt;
&lt;p&gt;That's why I decided to make a tiny userscript offering a new section right into the sidebar, called &lt;code&gt;Labels proposal&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Labels proposal" src="https://narcisse.synbioz.com/attachments/ca5a6f25-4a07-454f-962f-cf65c0466540/variants/6911c1ba-6a0f-4dce-acb3-04fc319815fb/src" /&gt;&lt;/p&gt;
&lt;p&gt;Based on the current label and some predefined «states machine», the interface offers to apply the next label.&lt;/p&gt;
&lt;p&gt;Clicking on a proposed label will set it on the issue and remove the other ones (only those related to the states we defined).&lt;/p&gt;
&lt;p&gt;Removing the labels (by simulating a click on the cross) has the advantage of refreshing this part of the toolbar.&lt;/p&gt;
&lt;h3&gt;Show me the code&lt;/h3&gt;
&lt;p&gt;This userscript needs you to install an extension offering the ability to run userscripts beforehand.&lt;/p&gt;
&lt;p&gt;I have been a long time user of &lt;a href="https://fr.wikipedia.org/wiki/Greasemonkey"&gt;Greasemonkey&lt;/a&gt; that now seems to be superseeded by &lt;a href="https://www.tampermonkey.net/"&gt;Tampermonkey&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So this is just JS code that will run inside your browser. Tampermonkey lets you decide when you want to run the script. Could be once the document starts to load, ends to load, or everything has been loaded and executed (especially other scripts). There is also a few other choices &lt;a href="https://www.tampermonkey.net/documentation.php#_run_at"&gt;detailed in the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I chose the last one because I needed to ensure labels have been loaded before trying to manipulate them.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// ==UserScript==&lt;/span&gt;
&lt;span class="c1"&gt;// @name         GitLab label proposal&lt;/span&gt;
&lt;span class="c1"&gt;// @namespace    https://www.synbioz.com/&lt;/span&gt;
&lt;span class="c1"&gt;// @version      0.2.0&lt;/span&gt;
&lt;span class="c1"&gt;// @description  Label proposal for GitLab&lt;/span&gt;
&lt;span class="c1"&gt;// @author       Martin Catty&lt;/span&gt;
&lt;span class="c1"&gt;// @include      /^https:\/\/git\.synbioz\.com/.*/issues/\d&lt;/span&gt;
&lt;span class="c1"&gt;// @icon         https://www.google.com/s2/favicons?domain=gitlab.com&lt;/span&gt;
&lt;span class="c1"&gt;// @run-at       document-idle&lt;/span&gt;
&lt;span class="c1"&gt;// @grant        none&lt;/span&gt;
&lt;span class="c1"&gt;// ==/UserScript==&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;states&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"to do"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"doing"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"doing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"to validate::functionally"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"to validate::functionally"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"to do"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"to validate::technically"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"to validate::technically"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"to deploy::staging"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"to deploy::staging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"to deploy::production"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;initialStates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;states&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transitions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;states&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".labels"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Create a map from existing labels, using this form :&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// { "label" =&amp;gt; [node1, node2] }&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;labelsToNodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".labels .gl-label-text"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sibling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextElementSibling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sibling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sibling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"::"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// we don't want to keep track of label's node not part&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// of the states we are watching&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialStates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nextLabels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labelsToNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transitions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="sb"&gt;      &amp;lt;div class="title hide-collapsed gl-mb-3"&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;        Labels proposal&lt;/span&gt;
&lt;span class="sb"&gt;      &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;    `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nextLabels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="sb"&gt;          &amp;lt;div class="hide-collapsed value issuable-show-labels js-value has-labels"&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;            &amp;lt;span data-qa-selector="selected_label_content" data-qa-label-name="to do" class="gl-label gl-label-text-light"&lt;/span&gt;
&lt;span class="sb"&gt;              style="--label-background-color:#D10069; --label-inset-border:inset 0 0 0 2px #D10069;"&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;              &amp;lt;a href="#" class="gl-link gl-label-link"&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;                &amp;lt;span class="gl-label-text label-proposal"&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;                  &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="sb"&gt;                &amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;              &amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;            &amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;          &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;        `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"block"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csrf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'meta[name="csrf-token"]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".label-proposal"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`/api/v4/projects/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/issues/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageTypeId&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;?add_labels=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"x-csrf-token"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csrf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Accept&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;withCredentials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;// remove existing labels&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;labelsToNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// there can be 2 nodes (including the scope), we only need one,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// they have the same parent&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cross&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextElementSibling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nx"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You may not be able to use this script as is, as it's meant for our own workflow, but it should be easy to update and enhance.&lt;/p&gt;
&lt;p&gt;I hope this will help some, do not hesitate to leave a message on &lt;a href="https://twitter.com/synbioz"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Webpacker : the highway to hell</title>
    <id>https://martin.catty.fr/blog/webpacker-the-highway-to-hell/</id>
    <link href="https://martin.catty.fr/blog/webpacker-the-highway-to-hell/" rel="alternate"/>
    <updated>2021-05-07T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Un outil pour les gouverner tous&lt;/h3&gt;
&lt;p&gt;Note I: dans cet article quand je parle de production il s'agit du mode
production plutôt que d'environnement de production.&lt;/p&gt;
&lt;p&gt;Note II : &lt;a href="https://rubygems.org/gems/webpacker"&gt;Webpacker&lt;/a&gt; est une gem
permettant d'utiliser Webpack dans une application Ruby.&lt;/p&gt;
&lt;p&gt;Note III : Juste parce que j'aime bien &lt;a href="https://www.lemonde.fr/m-le-mag/article/2021/03/25/faut-il-en-finir-avec-les-chiffres-romains_6074436_4500055.html"&gt;les chiffres
romains&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nos applications Rails, dès lors qu'elles quittent nos machines, tournent
toujours dans un mode production, aussi bien sur un environnement de
pré-production que de revue.&lt;/p&gt;
&lt;p&gt;Lorsque nous démarrons de nouveaux projets, nous utilisons notre générateur de
projets, répondant au doux nom d'Opsible (car utilisant Ansible) et qui nous
permet de mettre en place une application prête à l'emploi, aussi bien
localement qu'en production.&lt;/p&gt;
&lt;p&gt;Tous nos projets utilisant
&lt;a href="https://www.synbioz.com/blog/tech/premiers-pas-avec-docker"&gt;Docker&lt;/a&gt;, l'outil
est en charge de mettre en place une configuration fonctionnelle.&lt;/p&gt;
&lt;p&gt;Il s'appuie également sur des &lt;a href="https://www.synbioz.com/blog/tech/les-templates-rails"&gt;templates
Rails&lt;/a&gt; pour nous
permettre de créer de pures API ou des monolithes.&lt;/p&gt;
&lt;p&gt;Lors d'une nouvelle mise à jour d'Opsible s'est reposée la sempiternelle
question : Sprockets et/ou Webpacker ?&lt;/p&gt;
&lt;h3&gt;Sprockets et/ou Webpacker ?&lt;/h3&gt;
&lt;p&gt;Si vous lisez le guide Ruby on Rails dédié à Webpacker la réponse est très
claire (&lt;code&gt;&amp;lt;/troll&amp;gt;&lt;/code&gt;) : vous pouvez utiliser les deux pour faire … les mêmes
choses.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;New Rails apps are configured to use webpack for JavaScript and Sprockets for
CSS, although you can do CSS in webpack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dans l'équipe notre consensus était clair : Webpack a plutôt vocation à être
utilisé lorsqu'on intègre des app riches dans notre monolithe.&lt;/p&gt;
&lt;p&gt;Comprenez que dès que l'on souhaite &lt;a href="https://www.synbioz.com/blog/tech/introduction-a-vuejs-construisons-une-memebox"&gt;intégrer des frameworks front type
Vue.js&lt;/a&gt;
nous mettons en place Webpacker.&lt;/p&gt;
&lt;p&gt;Sauf que celui-ci venait en addition de Sprockets plutôt qu'en remplacement.&lt;/p&gt;
&lt;h3&gt;Qui peut le plus peut le moins&lt;/h3&gt;
&lt;p&gt;Dès lors qu'on bascule dans ce &lt;em&gt;setup&lt;/em&gt; qui mix les deux approches, ceux qui
travaillent sur des parties sans Webpack ont un peu l'impression de se faire
avoir.&lt;/p&gt;
&lt;p&gt;Ils font tourner les processus nécessaires à Webpacker, notamment un serveur
dédié à la recompilation des assets, sans en profiter.&lt;/p&gt;
&lt;p&gt;En effet, Webpack offre des avantages non négligeables comme le &lt;em&gt;hot reloading&lt;/em&gt; et
le &lt;em&gt;hot module replacement&lt;/em&gt;, offrant une expérience de développement bien plus
fluide.&lt;/p&gt;
&lt;p&gt;Dès lors, nous avons décidé pour nos nouveaux projets de ne garder que
Webpacker.&lt;/p&gt;
&lt;h3&gt;De la bonne configuration de Webpacker&lt;/h3&gt;
&lt;p&gt;Avec Webpacker la règle est simple : tant que vous n'êtes pas en production vous
n'êtes pas sûr que ça fonctionne.&lt;/p&gt;
&lt;p&gt;Là où Rails nous a habitué à son &lt;em&gt;convention over configuration&lt;/em&gt;, dans le
contexte de Webpack il semble n'y avoir étonnamment pas de consensus fort sur la
bonne façon d'organiser ses fichiers, quand l'utiliser, etc.&lt;/p&gt;
&lt;p&gt;Pour matérialiser les soucis qu'on peut rencontrer lors de sa mise en place je
vais prendre un cas d'usage dans l'air du temps : l'intégration de
&lt;a href="https://tailwindcss.com/"&gt;Tailwind&lt;/a&gt; dans notre application.&lt;/p&gt;
&lt;p&gt;La configuration de Webpacker (&lt;code&gt;config/webpacker.yml&lt;/code&gt;) est découpée en
environnements, vous aurez donc probablement une configuration &lt;code&gt;development&lt;/code&gt; et
une &lt;code&gt;production&lt;/code&gt; à minima (nous y reviendrons).&lt;/p&gt;
&lt;p&gt;Notre objectif est de ne garder dans notre &lt;em&gt;layout&lt;/em&gt; que les deux balises suivantes
:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stylesheet_pack_tag&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data-turbolinks-track"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reload"&lt;/span&gt;
&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;javascript_pack_tag&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data-turbolinks-track"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notre configuration par défaut de Webpacker est la suivante :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;source_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;app/javascript&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;source_entry_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;packs&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;public_root_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;public&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;public_output_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;packs&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;cache_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tmp/cache/webpacker&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;webpack_compile_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;check_yarn_integrity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Rien de complexe ici, par défaut nos fichiers seront recherchés dans
&lt;code&gt;app/javascript&lt;/code&gt;. Les « manifestes » devront se trouver dans le dossier &lt;code&gt;packs&lt;/code&gt;.
Les dossiers de sortie des fichiers compilés seront dans &lt;code&gt;public/packs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sans perdre de temps nous voilà en route pour intégrer Tailwind. L'un des
avantages de Webpacker est de pouvoir facilement intégrer des bibliothèques
publiées sous forme de paquets NPM.&lt;/p&gt;
&lt;p&gt;Ni une ni deux :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;yarn&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;tailwindcss
yarn&lt;span class="w"&gt; &lt;/span&gt;tailwind&lt;span class="w"&gt; &lt;/span&gt;init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Puis on configure notre fichier &lt;code&gt;postcss.config.js&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"postcss-import"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// on ajoute cette ligne uniquement&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"postcss-flexbugs-fixes"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"postcss-preset-env"&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;autoprefixer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;flexbox&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-2009"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On part sur une arborescence de la sorte pour organiser notre JavaScript :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/javascript
├── channels
│   ├── consumer.js
│   └── index.js
└── packs
    └── application.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et celle ci pour nos CSS :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/stylesheet
└── app.scss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans notre manifeste &lt;code&gt;application.js&lt;/code&gt; on va référencer nos fichiers CSS, c'est
une pratique normale de Webpack qui fonctionne sur un mécanisme d'import.&lt;/p&gt;
&lt;p&gt;Dès lors que les fichiers CSS ont été référencés, ils sont utilisables dans
notre application et cela fonctionne même sans directive d'import de notre
manifeste CSS dans notre &lt;em&gt;layout&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@rails/ujs"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Turbolinks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"turbolinks"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ActiveStorage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@rails/activestorage"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"channels"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../../stylesheet/app.scss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// on ajoute cette ligne&lt;/span&gt;

&lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;Turbolinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;ActiveStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le torse bombé on relance notre serveur webpack et là, c'est le drame.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Erreur postcss
8" src="https://narcisse.synbioz.com/attachments/dd2ee879-224a-4b7e-884e-aaf2923df072/variants/54b291c2-04f9-46c8-ad9f-c269ad6dab6d/src" /&gt;&lt;/p&gt;
&lt;p&gt;Visiblement Tailwind veut la version 8 de PostCSS. Mais cette version n'est pas
compatible avec la version stable actuelle de Webpacker (5.3).&lt;/p&gt;
&lt;p&gt;Combatif, on ne lâche pas l'affaire, il se trouve qu'il y a une version de
Tailwind compatible avec PostCSS 7.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn remove tailwindcss
yarn add tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Magnifique, on peut maintenant écrire des styles dans notre fichier
&lt;code&gt;app/stylesheet/app.scss&lt;/code&gt;, ceux-ci seront appliqués à la volée.&lt;/p&gt;
&lt;p&gt;Profitons-en pour importer également Tailwind (qui n'est pas encore chargé à ce
stade), on aura au final dans notre fichier &lt;code&gt;app.scss&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tailwindcss/base"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tailwindcss/utilities"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tailwindcss/components"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cela fonctionne bien, avec un bémol :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;webpack_1  | ℹ ｢wdm｣: Compiled successfully.
webpack_1  | ℹ ｢wdm｣: Compiling...
webpack_1  | ℹ ｢wdm｣: Hash: e0f5684b0fd5ecd26488
webpack_1  | Version: webpack 4.46.0
webpack_1  | Time: 4470ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quasiment 5 secondes de compilation, et ce à chaque fois qu'on va introduire une
directive dans le fichier. Cela tient au fait que Tailwind contient une
tétrachiée de classes utilitaires, prêtes à l'emploi et mises à disposition même
si on ne s'en sert pas.&lt;/p&gt;
&lt;p&gt;Pour y palier on va découper notre fichier en deux :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/stylesheet
├── app.scss
└── tailwind.scss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Le nouveau fichier &lt;code&gt;tailwind.scss&lt;/code&gt; ne contiendra que les directives relatives à
ce dernier. Lorsqu'on édite notre fichier &lt;code&gt;app.scss&lt;/code&gt;, on revient à des temps
acceptables :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;webpack_1  | ℹ ｢wdm｣: Compiled successfully.
webpack_1  | ℹ ｢wdm｣: Compiling...
webpack_1  | ℹ ｢wdm｣: Hash: 9f7a514f5e27f38ab57e
webpack_1  | Version: webpack 4.46.0
webpack_1  | Time: 156ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maintenant que tout est en ordre il faut qu'on prépare notre configuration pour
la production. Si la balise suivante est inutile en local, elle le sera en
production :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;= stylesheet_pack_tag  &amp;quot;application&amp;quot;, media: &amp;quot;all&amp;quot;, &amp;quot;data-turbolinks-track&amp;quot;: &amp;quot;reload&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On va donc créer notre manifeste :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/javascript/packs
├── application.js
└── application.scss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Qui contient les même instructions :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../../stylesheet/tailwind.scss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../../stylesheet/app.scss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On rajoute quelques directives dans notre fichier &lt;code&gt;app.scss&lt;/code&gt; et là surprise,
notre build prend de nouveau 5 secondes !&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;webpack_1  | ℹ ｢wdm｣: Compiling...
webpack_1  | ℹ ｢wdm｣: Hash: 36fdd1b07e74458fd268
webpack_1  | Version: webpack 4.46.0
webpack_1  | Time: 4754ms
webpack_1  | Built at: 04/27/2021 2:35:27 PM
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et oui dès lors que notre manifeste est déclaré, l'ensemble des fichiers qui y
sont référencés vont être surveillés et compilés.&lt;/p&gt;
&lt;p&gt;Pour éviter cela nous avons décidé de déclarer le manifeste dans un dossier qui
ne sera utilisé que pour la production.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;production:
  &amp;lt;&amp;lt;: *default

  additional_paths: [&amp;quot;app/stylesheet&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;app/stylesheet
├── app.scss
├── packs
│   └── application.scss
└── tailwind.scss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;S'en est tout de mes pérégrinations dans le pays merveilleux de Webpack(er). Je
vous ai toutefois laissé avec un problème à résoudre, celui de &lt;a href="https://purgecss.com/"&gt;retirer les CSS
inutilisées&lt;/a&gt; dans votre build de production (autrement le
fichier Tailwind pèse quelque 3Mo non compressé).&lt;/p&gt;
&lt;p&gt;Si vous avez des avis / astuces / bonnes pratiques sur la façon de dompter ce
monstre qu'est Webpack, n'hésitez pas à engager la conversation sur
&lt;a href="https://twitter.com/synbioz"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Tour d'horizon de dry-type, dry-struct et dry-validation</title>
    <id>https://martin.catty.fr/blog/tour-dhorizon-de-dry-type-dry-struct-et-dry-validation/</id>
    <link href="https://martin.catty.fr/blog/tour-dhorizon-de-dry-type-dry-struct-et-dry-validation/" rel="alternate"/>
    <updated>2021-05-07T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;En attendant la sortie d'Hanami v2, qui possède quelques gems dry-rb en dépendances et après qu'Émilie nous a fait découvrir &lt;a href="https://www.synbioz.com/blog/tech/manipuler-ses-donnees-avec-dry-transformer"&gt;dry-transformer&lt;/a&gt;, passons à &lt;a href="https://github.com/dry-rb/dry-struct"&gt;dry-struct&lt;/a&gt;, &lt;a href="https://github.com/dry-rb/dry-types"&gt;dry-types&lt;/a&gt; et &lt;a href="https://github.com/dry-rb/dry-validation"&gt;dry-validation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;J'ai récemment eu l'occasion de les mettre en œuvre dans le cadre d'une application Ruby on Rails assez conséquente.&lt;/p&gt;
&lt;p&gt;Je suis toujours frileux à l'idée d'ajouter de nouvelles dépendances, surtout dans des &lt;em&gt;codebases&lt;/em&gt; déjà volumineuses ; mais les gems dry-rb ont cet avantage d'avoir elles-mêmes très peu, voire aucune dépendance.&lt;/p&gt;
&lt;p&gt;Dans le contexte de dry-struct on a :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_runtime_dependency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dry-core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~&amp;gt; 0.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;= 0.5"&lt;/span&gt;
&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_runtime_dependency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dry-types"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~&amp;gt; 1.5"&lt;/span&gt;
&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_runtime_dependency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ice_nine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~&amp;gt; 0.11"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sachant que j'avais aussi besoin de &lt;code&gt;dry-types&lt;/code&gt; ont est somme toute sur un nombre de dépendances limité et souvent maintenues par les mêmes personnes.&lt;/p&gt;
&lt;p&gt;Si vous vous dites que toutes ces gems semblent assez similaires et répondent aux mêmes usages c'est tout à fait normal pour une première impression.&lt;/p&gt;
&lt;p&gt;Mais dry a cette philosophie très unixienne d'un outil par usage, quand bien même il semble y avoir des recouvrements.&lt;/p&gt;
&lt;h3&gt;Pourquoi utiliser ces gems ?&lt;/h3&gt;
&lt;p&gt;Le contexte métier était de récupérer une entrée avec plusieurs paramètres pour établir une simulation financière.&lt;/p&gt;
&lt;p&gt;Dans ces entrées on pouvait avoir le genre, le métier, les revenus, etc.
Certains paramètres étaient optionnels, d'autres non. Certaines valeurs étaient libres, d'autres contraintes. Pour certains paramètres on voulait des valeurs par défaut, mais pas toujours.&lt;/p&gt;
&lt;p&gt;Bref rien de très exotique, mais je voulais évidemment éviter de me retrouver avec une action énorme dans un contrôleur enchainant les &lt;code&gt;if&lt;/code&gt; / &lt;code&gt;else&lt;/code&gt; et plutôt avoir une classe dédiée qui serait facile à tester en isolation.&lt;/p&gt;
&lt;p&gt;Dans Rails on aurait pu partir sur &lt;code&gt;ActiveModel&lt;/code&gt;, mais ici j'ai préféré m'aventurer avec &lt;code&gt;Dry::Struct&lt;/code&gt;, dans l'optique de créer un service auquel je passerai directement mon entrée depuis mon contrôleur.&lt;/p&gt;
&lt;p&gt;Dans l'action de mon contrôleur j'ai simplement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;controller_action&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;simulator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;RetirementSimulator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;gender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:gender&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;career_start_year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:career_start_year&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;net_mensual_salary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:net_mensual_salary&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;forecast_annual_increase_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:forecast_annual_increase_rate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;executive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:executive&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;established_official&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:established_official&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;desired_mensual_saving_efforts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:desired_mensual_saving_efforts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;desired_age_at_retirement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:desired_age_at_retirement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simulator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;J'utilise donc uniquement l'action comme un passe-plat. En termes de tests, l'idée était de créer des « profils », c'est-à-dire des jeux de paramètres, certains cohérents, d'autres non et d'en vérifier la sortie.&lt;/p&gt;
&lt;p&gt;Avec ce &lt;em&gt;setup&lt;/em&gt; mes tests offrent l'avantage d'être complètement indépendants du reste de l'application et je n'ai globalement que ma classe à tester pour m'assurer que tout fonctionne comme attendu.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RetirementSimulator.build&lt;/code&gt; renvoie une instance d'objet qui peut varier. Si les paramètres ne sont pas valides, il s'agira d'un objet de type &lt;code&gt;NullRetirementSimulator&lt;/code&gt;. Autrement  il peut s'agir d'un objet de type &lt;code&gt;OpenStruct&lt;/code&gt;, qui sera en charge de renvoyer le payload et le statut au même format.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;OpenStruct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;as_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;dry-types&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dry-types&lt;/code&gt; va nous permettre, entre autres, de vérifier et/ou caster automatiquement nos paramètres dans le format attendu.&lt;/p&gt;
&lt;p&gt;Dans un mode strict (ex : &lt;code&gt;attribute :age,  Types::Strict::Integer&lt;/code&gt;), si vous passez une valeur non autorisée pour votre attribut vous vous ferez jeter.&lt;/p&gt;
&lt;p&gt;Dans un mode coercible, &lt;code&gt;dry-types&lt;/code&gt; essaiera de caster pour vous les valeurs passées. Si vous passez la chaine de caractères &lt;code&gt;"18"&lt;/code&gt; pour l'âge, il la convertira automatiquement en entier.&lt;/p&gt;
&lt;p&gt;Pour cela il utilisera uniquement les méthodes mises à disposition par Ruby au niveau &lt;em&gt;kernel&lt;/em&gt;, par exemple &lt;code&gt;"18".to_i&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour les types qui ne sont pas directement convertibles avec Ruby il faut utiliser &lt;code&gt;Types::Params&lt;/code&gt;, par exemple &lt;code&gt;Types::Params::Bool&lt;/code&gt; que l'on va retrouver juste après et qui peut transformer &lt;code&gt;"1"&lt;/code&gt; en &lt;code&gt;true&lt;/code&gt; par exemple.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dry-types&lt;/code&gt; permet aussi d'ajouter des contraintes sur les valeurs, gérer des énumérateurs, des valeurs optionnelles, bref toutes les choses dont on va avoir besoin.&lt;/p&gt;
&lt;h3&gt;dry-struct&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dry-struct&lt;/code&gt; est lui construit au-dessus de &lt;code&gt;dry-types&lt;/code&gt;. Au lieu d'utiliser &lt;code&gt;dry-types&lt;/code&gt; comme un &lt;em&gt;mixin&lt;/em&gt; vous pouvez créer votre propre classe qui disposera des méthodes de &lt;code&gt;dry-types&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Étant donné qu'on crée un service utilisable en isolation, &lt;a href="https://www.youtube.com/watch?v=VelvguwXntg"&gt;ça colle plutôt pas mal&lt;/a&gt; 🙂&lt;/p&gt;
&lt;p&gt;Voilà quelques morceaux choisis de notre code que l'on détaillera juste après :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;RetirementSimulator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Struct&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;STATUS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'employee'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'independent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'official'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;transform_types&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default?&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;constructor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nil?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Undefined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optional&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:career_start_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Coercible&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optional&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;attribute?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:average_annual_increase_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Coercible&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:executive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Bool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optional&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:year_of_birth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="ss"&gt;:profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="ss"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Du côté des évidences, la partie &lt;code&gt;enum&lt;/code&gt; nous permet de gérer notre attribut &lt;code&gt;status&lt;/code&gt; qui prendra l'une des valeurs autorisées.&lt;/p&gt;
&lt;p&gt;On peut bien sûr demander à un attribut d'être d'un certain type (interne à Ruby ou non), c'est le cas ici avec &lt;code&gt;Dry.Types.Instance(User)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La notion de &lt;code&gt;attribute?&lt;/code&gt; (avec le ?) permet de définir qu'un attribut est optionnel, si la clé est absente la validation n'échouera pas.&lt;/p&gt;
&lt;p&gt;À ne pas confondre avec &lt;code&gt;optional&lt;/code&gt; qui indique que la valeur n'est pas requise, mais dans ce cas la clé doit tout de même être présente !&lt;/p&gt;
&lt;p&gt;Un attribut peut être optionnel mais avec une valeur par défaut. Si la clé n'est pas passée on utilisera alors la valeur prédéfinie (c'est le cas de &lt;code&gt;average_annual_increase_rate&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Dans le cas contraire on utilisera ce qui est passé (si cela respecte le type ou que c'est castable).&lt;/p&gt;
&lt;p&gt;Vous voyez dans l'exemple différents types de données, &lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Float&lt;/code&gt; et &lt;code&gt;Bool&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dans &lt;code&gt;dry-types&lt;/code&gt; une valeur &lt;code&gt;nil&lt;/code&gt; est considérée comme valable, elle ne sera donc pas résolue sur la valeur par défaut, ce qui n'est pas forcément ce qu'on veut.&lt;/p&gt;
&lt;p&gt;C'est un comportement qui a changé selon les versions et qui a été &lt;a href="https://github.com/dry-rb/dry-struct/issues/90"&gt;sujet à discussions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Au passage, c'est un point qui peut être irritant dans dry, il faut être vigilant sur des comportements qui peuvent changer d'une mineure à l'autre et que vous aurez parfois du mal à retrouver dans la documentation.&lt;/p&gt;
&lt;p&gt;C'est notre bloc &lt;code&gt;transform_types&lt;/code&gt;, à première vue pas très élégant, qui permet de changer ce comportement et d'utiliser la valeur par défaut si elle existe quand on reçoit &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Je n'ai pas inventé l'eau chaude sur ce point, &lt;a href="https://dry-rb.org/gems/dry-struct/1.0/recipes/#resolving-default-values-on-code-nil-code"&gt;c'est la méthode officielle conseillée&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Gérer les objets invalides&lt;/h3&gt;
&lt;p&gt;Dans le cas où nos paramètres ne respecteraient pas notre spécification, le constructeur nous renverra une exception &lt;code&gt;Dry::Struct::Error&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ce qui nous permet dans notre méthode build, appelée depuis notre constructeur, de gérer cela très simplement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;simulation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# here comes the magic&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;NullRetirementSimulator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:unprocessable_entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En effet, en héritant de &lt;code&gt;dry-struct&lt;/code&gt; j'hérite d'un constructeur par défaut qui me permet de directement passer mes arguments pour construire un objet disposant d'accesseurs sur les attributs mis en place.&lt;/p&gt;
&lt;p&gt;Dans le cas où mon objet est invalide, car ne respectant pas les contraintes définies, j'utilise le pattern &lt;code&gt;Null Object Pattern&lt;/code&gt; (&lt;code&gt;NullRetirementSimulator&lt;/code&gt; est une bête classe avec 2 accesseurs) qui permettra de renvoyer les valeurs et statuts HTTP voulus dans le cas où mon entrée est invalide, sans avoir à le gérer comme un cas particulier dans l'action de mon contrôleur.&lt;/p&gt;
&lt;h3&gt;Et dry-validation alors ?&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=Ri7GzCUTC5s"&gt;Ah&lt;/a&gt; ! Je vous ai caché un petit morceau dans le &lt;em&gt;snippet&lt;/em&gt; ci-dessus, la partie "&lt;em&gt;here comes the magic&lt;/em&gt;".&lt;/p&gt;
&lt;p&gt;Son travail est de, selon les paramètres passés, appeler le bon simulateur (ce sont des simulateurs différents selon que la personne ait travaillé dans le privé ou dans le public, etc.).&lt;/p&gt;
&lt;p&gt;J'utilise donc le pattern adapter en m'assurant que mes simulateurs, quels qu'ils soient, répondent à la même interface. Ma classe &lt;code&gt;RetirementSimulator&lt;/code&gt; agit comme un routeur.&lt;/p&gt;
&lt;p&gt;Au sein de ces simulateurs spécifiques je veux valider d'autres choses, qui ne sont plus du domaine des paramètres reçus via HTTP mais uniquement de la logique métier.&lt;/p&gt;
&lt;p&gt;Pour cela on va définir un contrat :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;RetirementSimulatorContract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Contract&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;STARTING_YEAR_TO_COMPUTE_SUPPLEMENT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1973&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:year_of_birth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:career_start_year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:career_start_year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"career has to start after 1972"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;STARTING_YEAR_TO_COMPUTE_SUPPLEMENT&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:career_start_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:year_of_birth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:career_start_year&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:year_of_birth&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"is too early, can't start working before 14"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notre contrat va valider que la personne qui lance le simulateur n'indique pas avoir commencé à travailler avant 14 ans ni avant 1972.&lt;/p&gt;
&lt;p&gt;Pour cela &lt;code&gt;Dry::Validation::Contract&lt;/code&gt; nous permet de définir des &lt;code&gt;rules&lt;/code&gt; qui vont prendre un ou plusieurs paramètres en entrée.&lt;/p&gt;
&lt;p&gt;On mixe ici des valeurs qui viennent d'une part de notre base de données (&lt;code&gt;year_of_birth&lt;/code&gt;, qui vient de notre objet &lt;code&gt;user&lt;/code&gt;) et d'autre part de nos paramètres (&lt;code&gt;career_start_year&lt;/code&gt;) ; on voit donc bien qu'on n'est plus uniquement dans une logique de validation d'entrée.&lt;/p&gt;
&lt;p&gt;Pour invoquer ce contrat c'est aussi simple que :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;RetirementSimulatorContract&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;year_of_birth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simulation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year_of_birth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;career_start_year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simulation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;career_start_year&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les éventuelles erreurs se trouveront dans &lt;code&gt;res.errors&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Ces gems dry offrent une manière élégante (à mon avis) de garder du code propre et testable de façon isolée. Qui plus est, le nombre limité de dépendances permet de facilement venir greffer ces outils dans une &lt;em&gt;codebase&lt;/em&gt; existante.&lt;/p&gt;
&lt;p&gt;Cela peut donc être un excellent moyen de tirer vers le haut la qualité d'une base de code ayant déjà un peu de vécu de façon progressive et sans avoir à modifier l'existant.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Pas à pas pour héberger son site sur S3 avec un certificat SSL</title>
    <id>https://martin.catty.fr/blog/pas-a-pas-pour-heberger-son-site-sur-s3-avec-un-certificat-ssl/</id>
    <link href="https://martin.catty.fr/blog/pas-a-pas-pour-heberger-son-site-sur-s3-avec-un-certificat-ssl/" rel="alternate"/>
    <updated>2017-08-24T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;La mode est au statique et c'est une bonne nouvelle !&lt;/p&gt;
&lt;p&gt;Avoir besoin de gérer un serveur applicatif pour servir un site de contenu n'a pas un grand intérêt.
Il existe à l'heure actuelle une flopée de générateurs qui vous permettront localement de démarrer un serveur pour rédiger votre contenu, apercevoir le rendu puis générer le site en HTML avant de le déployer.&lt;/p&gt;
&lt;p&gt;L'objet de cet article n'est pas de se focaliser sur ces outils, mais si vous avez besoin de pointeurs sachez que &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; ou &lt;a href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; font le job.&lt;/p&gt;
&lt;h3&gt;Pourquoi ?&lt;/h3&gt;
&lt;p&gt;Imaginons que vous ayez un site HTML à héberger, soit issu de la génération d'un outil, soit parce que vous écrivez directement vos pages HTML. Comment l'héberger simplement ?&lt;/p&gt;
&lt;p&gt;Vous n'avez pas envie d'aller configurer un Nginx, encore moins Apache, ni même de prendre un serveur spécialement pour ça.&lt;/p&gt;
&lt;p&gt;Vous savez que Let's Encrypt c'est cool mais ça fait 3 fois que vous oubliez de renouveler vos certificats à l'issue des 3 mois et votre fichu cron de renouvellement automatique ne fonctionne pas…&lt;/p&gt;
&lt;p&gt;Si vous ne vous sentez pas familier d'un de ces soucis, vous êtes sans aucun doute un barbu qui a automatisé toute son infra avec Terraform et qui a maintenant les doigts de pied en éventail devant son écran.&lt;/p&gt;
&lt;p&gt;Autrement voyons ensemble comment déployer simplement son site sur S3.&lt;/p&gt;
&lt;h3&gt;Héberger son site HTML&lt;/h3&gt;
&lt;p&gt;Partons d'une bête page HTML que je souhaite utiliser pour mon site web personnel :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello World !&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Créer un &lt;em&gt;bucket&lt;/em&gt; S3&lt;/h4&gt;
&lt;p&gt;Partons à l'abordage de l'austère interface d'AWS. Si vous n'avez pas encore de compte créez-en un, je vous épargne le pas à pas.&lt;/p&gt;
&lt;p&gt;Une fois connecté rendez-vous dans le service S3.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/01-s3.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/01-s3_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Maintenant créons un &lt;em&gt;bucket&lt;/em&gt; en utilisant comme nom celui de notre site, dans mon cas &lt;code&gt;martin.catty.fr&lt;/code&gt;. Il est important d'utiliser le DNS exact.
Libre à vous de choisir la région, j'ai gardé l'Irlande qui est le choix par défaut sur mon compte.&lt;/p&gt;
&lt;p&gt;On n'active aucune des propriétés type &lt;em&gt;versioning&lt;/em&gt;, journalisation, etc.&lt;/p&gt;
&lt;h4&gt;Créer un utilisateur dédié&lt;/h4&gt;
&lt;p&gt;Il est de bon ton de restreindre l'accès à vos &lt;em&gt;buckets&lt;/em&gt;. J'ai tendance à avoir un utilisateur dédié par &lt;em&gt;bucket&lt;/em&gt;, ce qui permet de limiter les dégâts en cas de compromission de l'accès (notamment une fuite de clés).
Nous nous rendons cette fois dans le service IAM de gestion d'identité de l'interface.&lt;/p&gt;
&lt;p&gt;On crée un utilisateur qui n'aura accès à AWS que par le biais de l'API, il ne pourra pas se connecter directement à l'interface.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/02-iam-create-user.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/02-iam-create-user_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;En termes d'autorisation, on attache directement une stratégie existante.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/03-iam-policy.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/03-iam-policy_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;En fait ma stratégie n'existe pas encore, donc je vais la créer via le bouton dédié. Je souhaite ajouter une police d'utilisation permettant à cet utilisateur d'accéder à ce &lt;em&gt;bucket&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Dans le nouvel onglet on choisit « Créer votre propre stratégie »&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/04-iam-custom-policy.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/04-iam-custom-policy_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Vous pouvez nommer votre stratégie comme bon vous semble, j'ai choisi &lt;code&gt;AccessToWebsiteBucket&lt;/code&gt;. Je vous laisse faire preuve d'imagination pour la description.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
    &amp;quot;Statement&amp;quot;: [
    {
        &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
        &amp;quot;Action&amp;quot;: &amp;quot;s3:*&amp;quot;,
        &amp;quot;Resource&amp;quot;: &amp;quot;arn:aws:s3:::martin.catty.fr&amp;quot;
    },
    {
        &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
        &amp;quot;Action&amp;quot;: &amp;quot;s3:*&amp;quot;,
        &amp;quot;Resource&amp;quot;: &amp;quot;arn:aws:s3:::martin.catty.fr/*&amp;quot;
    }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cette police me donne accès à la racine du &lt;em&gt;bucket&lt;/em&gt; et au reste. Je vous conseille de la tester avec le bouton « Valider la stratégie » avant de la créer.
Une fois terminé, revenez sur l'écran de création de l'utilisateur et rafraîchissez la liste des stratégies.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/05-refresh-policies.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/05-refresh-policies_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Je vois maintenant apparaître la police que je viens de créer, je la choisis et je passe à la suite. L'écran suivant est capital, il vous donne les informations de connexion relatives à votre utilisateur ; conservez-les précieusement elles ne seront plus accessibles une fois la page fermée.&lt;/p&gt;
&lt;h4&gt;Configuration du profil et utilisation du client AWS&lt;/h4&gt;
&lt;p&gt;Nous allons maintenant installer le client AWS. Vous pouvez l'installer via &lt;code&gt;brew install awscli&lt;/code&gt; sur OSX ou encore en utilisant pip, le gestionnaire de paquets de python.&lt;/p&gt;
&lt;p&gt;Il est temps de configurer nos &lt;em&gt;credentials&lt;/em&gt; (ID de clé d'accès et clé d'accès secrète) que nous avons récupéré de l'interface AWS.&lt;/p&gt;
&lt;p&gt;Pour ce faire nous allons créer le fichier &lt;code&gt;~/.aws/config&lt;/code&gt; et configurer notre profil :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[profile martin-catty]
aws_access_key_id = XXX
aws_secret_access_key = YYY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vous pouvez donner le nom que vous souhaitez à votre profil. Nous pouvons maintenant vérifier que nous avons accès au &lt;em&gt;bucket&lt;/em&gt; avec ce profil en utilisant le client AWS :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;aws&lt;span class="w"&gt; &lt;/span&gt;s3&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;s3://martin.catty.fr&lt;span class="w"&gt; &lt;/span&gt;--profile&lt;span class="w"&gt; &lt;/span&gt;martin-catty
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si tout se passe bien, la commande ne renverra… rien. C'est normal notre &lt;em&gt;bucket&lt;/em&gt; est vide. Poussons-y un fichier pour nous assurer que tout fonctionne.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;touch&lt;span class="w"&gt; &lt;/span&gt;/tmp/foo
aws&lt;span class="w"&gt; &lt;/span&gt;s3&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;/tmp/foo&lt;span class="w"&gt; &lt;/span&gt;s3://martin.catty.fr&lt;span class="w"&gt; &lt;/span&gt;--profile&lt;span class="w"&gt; &lt;/span&gt;martin-catty
upload:&lt;span class="w"&gt; &lt;/span&gt;../../../../../tmp/foo&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;s3://martin.catty.fr/foo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A priori tout est en ordre, si vous relancez la commande pour lister le contenu de votre &lt;em&gt;bucket&lt;/em&gt; vous y verrez votre fichier &lt;code&gt;foo&lt;/code&gt;.
Sur le même principe mettons-y notre fichier &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Activer l'hébergement sur le &lt;em&gt;bucket&lt;/em&gt; S3&lt;/h3&gt;
&lt;p&gt;Nous savons dorénavant mettre des fichiers dans un &lt;em&gt;bucket&lt;/em&gt;, activons l'hébergement de site avec S3. Pour ce faire, retour dans l'interface AWS, service S3, on clique sur notre &lt;em&gt;bucket&lt;/em&gt;, onglet propriété.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/06-s3-website-hosting.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/06-s3-website-hosting_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On active la propriété « Hébergement de site Web statique » en indiquant que le document d'index sera &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/07-s3-use-default-document.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/07-s3-use-default-document_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;En suivant le point de terminaison donné par AWS (dans mon cas &lt;a href="http://martin.catty.fr.s3-website-eu-west-1.amazonaws.com/"&gt;http://martin.catty.fr.s3-website-eu-west-1.amazonaws.com/&lt;/a&gt;), on aura droit à une 403. C'est normal, de base le reste du monde n'a pas accès à notre &lt;em&gt;bucket&lt;/em&gt;, fort heureusement.&lt;/p&gt;
&lt;p&gt;Il faut donc ajouter une police au niveau du &lt;em&gt;bucket&lt;/em&gt; cette fois, dans l'onglet « Autorisations ».&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;Version&amp;quot;:&amp;quot;2012-10-17&amp;quot;,
    &amp;quot;Statement&amp;quot;:[{
      &amp;quot;Sid&amp;quot;:&amp;quot;PublicReadGetObject&amp;quot;,
      &amp;quot;Effect&amp;quot;:&amp;quot;Allow&amp;quot;,
      &amp;quot;Principal&amp;quot;: &amp;quot;*&amp;quot;,
      &amp;quot;Action&amp;quot;:[&amp;quot;s3:GetObject&amp;quot;],
      &amp;quot;Resource&amp;quot;:[&amp;quot;arn:aws:s3:::martin.catty.fr/*&amp;quot;]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Celle-ci permet à n'importe qui de lire les fichiers dans le &lt;em&gt;bucket&lt;/em&gt;. En rafraîchissant on a maintenant notre index.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/08-browser-website-s3.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/08-browser-website-s3_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Mettre en place un certificat SSL gratuitement&lt;/h3&gt;
&lt;p&gt;C'est bien, mais on souhaite que le site soit disponible à travers HTTPS, sans avoir à acheter de certificats ni à gérer le renouvellement.&lt;/p&gt;
&lt;p&gt;Chez AWS il y a un service pour ça : Certificate Manager. Avant de démarrer le processus assurez-vous de basculer sur la région «Virginie du Nord» car c'est un pré-requis du service dans lequel nous allons utiliser notre certificat.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/09-use-us-est-for-cm.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/09-use-us-est-for-cm_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On suit le processus, qui nous enverra une confirmation par email pour vérifier que nous sommes bien propriétaires du site.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/10-certificate-manager.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/10-certificate-manager_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Une fois la requête approuvée, notre certificat est prêt. La subtilité de ces certificats est qu'on ne peut pas les récupérer pour les utiliser à l'extérieur d'AWS, &lt;em&gt;vendor locking&lt;/em&gt; quand tu nous tiens.&lt;/p&gt;
&lt;p&gt;Nous allons donc devoir le rattacher à une distribution CloudFront.&lt;/p&gt;
&lt;h3&gt;Utilisation de CloudFront avec un certificat SSL&lt;/h3&gt;
&lt;p&gt;CloudFront est un CDN, c'est-à-dire qu'il permet de diffuser des contenus au plus près des utilisateurs, en les répliquant dans différents points de présence.&lt;/p&gt;
&lt;p&gt;Cela permet de limiter la latence et les temps d'affichage quel que soit l'endroit dans le monde où se situent nos utilisateurs.&lt;/p&gt;
&lt;p&gt;Nous créons donc une distribution de type web.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/11-cm-web-distribution.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/11-cm-web-distribution_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pas d'inquiétude devant ce formulaire copieux, allons-y pas à pas. Dans le nom du domaine d'origine nous choisissons notre &lt;em&gt;bucket&lt;/em&gt; qui doit être listé.&lt;/p&gt;
&lt;p&gt;Niveau stratégie de protocole on choisit de rediriger automatiquement HTTP vers HTTPS.&lt;/p&gt;
&lt;p&gt;Plus bas dans les paramètres de distribution ont saisit comme domaine alternatif martin.catty.fr, on choisit le certificat précédemment créé et on crée la distribution.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/12-cloudfront-alternate-cname.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/12-cloudfront-alternate-cname_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Enfin nous définissons &lt;code&gt;index.html&lt;/code&gt; comme objet racine par défaut.&lt;/p&gt;
&lt;p&gt;Le processus de création de la distribution prend un peu de temps, notez toutefois dans un coin le DNS de la distribution CloudFront.&lt;/p&gt;
&lt;h3&gt;Configurer votre nom de domaine&lt;/h3&gt;
&lt;p&gt;Il ne reste plus qu'à faire pointer votre nom de domaine sur la distribution CloudFront, pour cela il vous suffit d'enregistrer votre nom de domaine en tant que CNAME de la distribution CloudFront.&lt;/p&gt;
&lt;p&gt;Si vous utilisez Route53 (le service de DNS d'Amazon Web Services), c'est un peu différent, créez un enregistrement de type A. Choisissez oui par Alias et vous devriez maintenant pouvoir choisir la distribution CloudFront dessous.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/13-route53.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/13-route53_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Voilà c'est terminé !&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/14-browser-website-cloudfront.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170823/14-browser-website-cloudfront_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Certes il y a quelques étapes à parcourir pour y arriver mais maintenant pour mettre à jour votre site, il suffit d'une simple commande avec le client AWS telle que :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aws s3 sync . s3://martin.catty.fr --profile martin-catty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cette commande va copier uniquement les fichiers modifiés vers votre &lt;em&gt;bucket&lt;/em&gt;. Temps de déploiement : 3 secondes !&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170824/15-it-works.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170824/15-it-works_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Les différents profils de marathoniens</title>
    <id>https://martin.catty.fr/blog/les-differents-profils-de-marathoniens/</id>
    <link href="https://martin.catty.fr/blog/les-differents-profils-de-marathoniens/" rel="alternate"/>
    <updated>2017-05-25T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Il y a quelques jours j'ai pu participer à l'édition 2017 de &lt;a href="http://www.laroutedulouvre.fr/"&gt;la route du Louvre&lt;/a&gt;, le marathon qui rallie Lille à Lens.&lt;/p&gt;
&lt;p&gt;Autant vous dire qu'un marathon ça donne un peu de temps pour réfléchir, surtout quand on a rien du profil Kenyan.&lt;/p&gt;
&lt;p&gt;Et justement en parlant de profil, tout ce temps ~~libre~~ de souffrance m'a permis d'identifier quelques-uns des profils qui se lancent dans l'aventure marathonienne. Laissez-moi vous les décrire.&lt;/p&gt;
&lt;h3&gt;Le vrai&lt;/h3&gt;
&lt;p&gt;Parti dans la première vague il démarre en trombe et donne littéralement l'impression de voler. À prendre en photo lors du départ car c'est la dernière fois qu'on le verra.&lt;/p&gt;
&lt;h3&gt;Celui qui reste dans la roue&lt;/h3&gt;
&lt;p&gt;Depuis le départ il vous suit. Au début vous vous dites que c'est sympa d'avoir quelqu'un qui court à la même vitesse que vous. Sauf qu'il va rester derrière tout du long, plus docile qu'un berger allemand dressé pendant 5 ans.&lt;/p&gt;
&lt;p&gt;Mais aussi beaucoup plus fourbe ; car oui, il va vous doubler dans les 100 derniers mètres.&lt;/p&gt;
&lt;h3&gt;Le campeur&lt;/h3&gt;
&lt;p&gt;Il démarre la course avec un sac à dos type barda militaire. Il est paré à toute éventualité et si le besoin s'en fait sentir il peut sortir sa tente et camper avec 3J de vivres à disposition. &lt;em&gt;Bonus point&lt;/em&gt; : la tente est équipée d'un paratonnerre.&lt;/p&gt;
&lt;h3&gt;Le feu follet&lt;/h3&gt;
&lt;p&gt;Il n'est pas tant là pour la course que pour se faire remarquer. Seul fada à partir au sprint il s'arrête au km 5 en prétextant le coup du lacet. Il abandonnera au km 10 à cause d'un caillou dans la chaussure cette fois.&lt;/p&gt;
&lt;h3&gt;Le déguisé&lt;/h3&gt;
&lt;p&gt;Parce qu'un marathon n'est pas assez difficile il se rajoute un petit challenge : le courir déguisé.
Pour ce «tout pour la blague» c'est mission réussie : fou rire garanti.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Marathon déguisé" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170522/dsc02289_thumb_450.png" /&gt;&lt;/p&gt;
&lt;h3&gt;Le faux&lt;/h3&gt;
&lt;p&gt;Comme le vrai il démarre sur les chapeaux de roues. Pourtant il ne semble pas beaucoup plus préparé que vous, ce qui finit de vous ruiner le peu de moral qu'il vous restait. Mais ça c'est avant que vous ne découvriez qu'il fait en fait le marathon en relais avec une équipe de 3 autres personnes.&lt;/p&gt;
&lt;h3&gt;Celui qui ne paye pas de mine&lt;/h3&gt;
&lt;p&gt;Avec son 1.50m et ses 45kg il semble un peu frêle. Pour autant ce petit roquet risque de vous laisser scotché au bitume. À prendre en photo avec le vrai s'il reste de la place sur la pellicule.&lt;/p&gt;
&lt;h3&gt;La petite vessie&lt;/h3&gt;
&lt;p&gt;Visiblement celui-là dispose d'une vessie aussi épaisse qu'un poix chiche desséché. Pour lui c'est pose pipi toutes les 5 min.
Se retrouve souvent dans la catégorie «Courant alternatif» également.&lt;/p&gt;
&lt;h3&gt;Le monument&lt;/h3&gt;
&lt;p&gt;Sorti tout droit d'un marvel, il mesure 2m et son profil est loin du profil «type». Sauf que son mollet fait la taille d'une cuisse et qu'il va aller jusqu'au bout.&lt;/p&gt;
&lt;h3&gt;Le à la va-y-comme-je-te-pousse&lt;/h3&gt;
&lt;p&gt;Faire un marathon ça ne doit pas être si dur après tout. A mi-chemin entre folie et inconscience ce marathonien débutant a pris le parti de ne pas s'entraîner. Une chance sur deux de le retrouver soit sur la ligne d'arrivée soit en pleine crise de tachycardie dans un ravin.&lt;/p&gt;
&lt;h3&gt;Le courant alternatif&lt;/h3&gt;
&lt;p&gt;Apparemment pas sur le même voltage que les autres il alterne entre lièvre et tortue. Peut-être qu'il fait le marathon en fractionné.&lt;/p&gt;
&lt;h3&gt;Le blessé&lt;/h3&gt;
&lt;p&gt;Qui se décompose en deux sous-catégories :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le vrai blessé. Juste surhumain, il a beau être blessé il ira jusqu'au bout. D'ailleurs il finirait à cloche pied s'il fallait.&lt;/li&gt;
&lt;li&gt;Le faux blessé. D'habitude il le boucle en 2h30, sans cette fichue douleur à la cheville…&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Le coup du lacet&lt;/h3&gt;
&lt;p&gt;Il s'arrête «juste une seconde» en vous glissant un «m'attend pas, je te rattrape». Évidemment vous ne le reverrez qu'à une prochaine soirée entre amis où il vous dira qu'il avait dû rentrer pour conduire son fils aux urgences pour cause d'arête avalée de travers.&lt;/p&gt;
&lt;h3&gt;Le malade&lt;/h3&gt;
&lt;p&gt;Quand tu le croises tu es à la fois bluffé et honteux d'être derrière car il porte un t-shirt du genre :&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170522/devant_toi.jpg"&gt;&lt;img alt="malade qui court le marathon" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170522/devant_toi_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Si vous aussi vous avez participé à un marathon dites-moi quels profils vous avez croisé ? Autrement je vous laisse imaginer dans quelle catégorie je me situe :)&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Docker on steroid avec xhyve et NFS</title>
    <id>https://martin.catty.fr/blog/docker-on-steroid-avec-xhyve-et-nfs/</id>
    <link href="https://martin.catty.fr/blog/docker-on-steroid-avec-xhyve-et-nfs/" rel="alternate"/>
    <updated>2017-04-25T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Je dois avouer que je suis vraiment fan des solutions de conteneurisation. Le fait de pouvoir packager ses applications est un vrai gage de sérénité.&lt;/p&gt;
&lt;p&gt;Dans un contexte SI où vous avez 10+ applications avec des technologies différentes, simplifier et harmoniser votre processus d'&lt;em&gt;onboarding&lt;/em&gt; et homogénéiser votre processus de déploiement est également un réel gain de productivité.&lt;/p&gt;
&lt;p&gt;Mais il faut garder un œil critique et les technologies comme Docker viennent avec leurs pendants de problèmes.&lt;/p&gt;
&lt;p&gt;Aujourd'hui je voudrais adresser le problème des performances dans un contexte OSX. Si vous vous demandez pourquoi je n'utilise pas Docker Native for Mac, c'est parce qu'il apporte lui aussi son lot de limitations dont deux vraiment bloquantes de mon point de vue : &lt;a href="https://github.com/docker/for-mac/issues/371"&gt;une mauvaise gestion du nettoyage des images&lt;/a&gt; et &lt;a href="https://docs.docker.com/docker-for-mac/networking/#known-limitations-use-cases-and-workarounds"&gt;des limitations réseau qui sont propres à OSX&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sympa ce petit paragraphe planqué dans la doc, n'est-ce pas ? L'espace est le nerf de la guerre avec Docker. Si vous n'êtes pas en capacité de nettoyer derrière vous, vous allez au devant de problème sérieux. Par ailleurs l'absence d'interface réseau dédiée empêche d'allouer des IP aux conteneurs et donc de faire du DNS.&lt;/p&gt;
&lt;p&gt;Si vous avez paré à ces problèmes en utilisant Docker for Mac, les commentaires sont ouverts.&lt;/p&gt;
&lt;p&gt;Avant d'avoir écarté Docker Native for Mac (qui n'existait pas encore quand j'ai commencé à utiliser Docker) je me suis assez naturellement penché sur l'utilisation de VirtualBox. Et j'y suis resté.&lt;/p&gt;
&lt;p&gt;À vrai dire tout fonctionne bien avec VirtualBox, à un bémol près, c'est lent. VirtualBox en lui même est un facteur mais ce n'est pas le principal. Ce qui pénalise vraiment les performances ce sont les I/O. En effet le partage de fichiers lors de l'utilisation d'un volume se fait au travers de la VM via vboxfs qui est horriblement lent.&lt;/p&gt;
&lt;p&gt;Une solution possible est d'utiliser la bonne veille &lt;a href="https://fr.wikipedia.org/wiki/Network_File_System"&gt;NFS&lt;/a&gt; pour gérer les I/O. Et pour se passer de VirtualBox on peut utiliser l'hyperviseur natif d'OSX (présent depuis Yosemite). Ces deux points peuvent être adressés en utilisant le driver &lt;a href="https://github.com/mist64/xhyve"&gt;xhyve&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Si vous préférez utiliser des outils magiques qui font tout pour vous, regardez du côté de &lt;a href="https://github.com/EugenMayer/docker-sync"&gt;docker-sync&lt;/a&gt;. Personnellement je préfère utiliser les outils bruts, car la magie a toujours un prix (© Rumplestiltskin).&lt;/p&gt;
&lt;h3&gt;Installation d'une machine virtuelle avec xhyve et NFS&lt;/h3&gt;
&lt;p&gt;On va commencer par installer le driver docker-machine :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;docker-machine-driver-xhyve
sudo&lt;span class="w"&gt; &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;root:wheel&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew&lt;span class="w"&gt; &lt;/span&gt;--prefix&lt;span class="k"&gt;)&lt;/span&gt;/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
sudo&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;u+s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew&lt;span class="w"&gt; &lt;/span&gt;--prefix&lt;span class="k"&gt;)&lt;/span&gt;/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On va maintenant pouvoir créer la VM à proprement parler :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker-machine&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;--xhyve-cpu-count&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--xhyve-memory-size&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8192&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--xhyve-disk-size&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;30000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--xhyve-experimental-nfs-share&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;xhyve&lt;span class="w"&gt; &lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En terme de ressources au niveau CPU et RAM je prends le parti de lui allouer la moitié des ressources disponibles sur ma machine et 30Go de disque. Pour l'ensemble de ces ressources il s'agit de la valeur max que la VM ne dépassera pas.&lt;/p&gt;
&lt;p&gt;La machine est provisionnée en utilisant boot2docker, comme lorsqu'on utilise VirtualBox. Elle est maintenant utilisable en l'état, toutefois je vais la customiser un peu, notamment pour en modifier la gestion des DNS et le réseau utilisé.&lt;/p&gt;
&lt;p&gt;En effet je souhaite garder ma VM utilisant VirtualBox pour en assurer la transition sans heurt. Je vais donc utiliser un sous réseau différent du réseau de base utilisé par boot2docker (172.17.0.0) pour faire cohabiter mes deux VM en simultané. Je souhaite aussi que mes conteneurs utilisent un résolveur qui tournera en conteneur dans la VM pour se retrouver par leur nom.&lt;/p&gt;
&lt;p&gt;Vous vous demandez peut-être pourquoi j'ai besoin de cela plutôt que d'utiliser des &lt;em&gt;links&lt;/em&gt;. Simplement car je ne souhaite pas que mes conteneurs ne puissent discuter qu'avec des services auxquels ils sont liés.&lt;/p&gt;
&lt;p&gt;Par exemple &lt;code&gt;app1.synbioz.dev&lt;/code&gt; doit pouvoir parler avec &lt;code&gt;app2.synbioz.dev&lt;/code&gt; sans que j'aie quoi que ce soit à déclarer au préalable. Pour ce faire j'ai besoin d'une résolution de nom.&lt;/p&gt;
&lt;p&gt;Pour appliquer ces modifications rentrons donc dans la VM :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker-machine&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;dev
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;-s
&lt;span class="c1"&gt;# mv /var/lib/boot2docker/profile /var/lib/boot2docker/profile.bak&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;À l'intérieur de la VM on est un peu pauvre en éditeur, on va donc se rabattre sur ce bon &lt;code&gt;cat&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# cat &amp;lt;&amp;lt; EOF &amp;gt; /var/lib/boot2docker/profile&lt;/span&gt;
&lt;span class="nv"&gt;EXTRA_ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="s1"&gt;--label provider=xhyve&lt;/span&gt;
&lt;span class="s1"&gt;--bip 172.18.0.1/16&lt;/span&gt;
&lt;span class="s1"&gt;--dns 172.18.0.1&lt;/span&gt;
&lt;span class="s1"&gt;--dns 8.8.8.8&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="nv"&gt;CACERT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/boot2docker/ca.pem
&lt;span class="nv"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'-H tcp://0.0.0.0:2376'&lt;/span&gt;
&lt;span class="nv"&gt;DOCKER_STORAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aufs
&lt;span class="nv"&gt;DOCKER_TLS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto
&lt;span class="nv"&gt;SERVERKEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/boot2docker/server-key.pem
&lt;span class="nv"&gt;SERVERCERT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/boot2docker/server.pem
EOF
&lt;span class="c1"&gt;# exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On redémarre la VM :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker-machine&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;et on se positionne sur le démon :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;docker-machine&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vérifions que tout fonctionne correctement en lançant un nginx dont le port 80 sera publié, c'est à dire mappé de l'hôte vers le conteneur.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;:80&lt;span class="w"&gt; &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On vérifie que tout fonctionne bien :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;open&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;docker-machine&lt;span class="w"&gt; &lt;/span&gt;ip&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cela devrait vous ouvrir la page de nginx sur l'IP &lt;code&gt;192.168.64.1&lt;/code&gt; (si vous créez différentes VM cette IP s'incrémentera).&lt;/p&gt;
&lt;p&gt;Notre objectif est maintenant de faire en sorte que &lt;code&gt;www.synbioz.dev&lt;/code&gt; nous affiche la page d'accueil de nginx sans avoir à publier de port sur l'hôte (ce qui nous empêcherait de faire tourner plusieurs nginx par exemple, problème existant avec Docker Native).&lt;/p&gt;
&lt;p&gt;Lançons maintenant notre résolveur avec dnsdock :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/var/run/docker.sock:/var/run/docker.sock&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;dnsdock&lt;span class="w"&gt; &lt;/span&gt;--restart&lt;span class="o"&gt;=&lt;/span&gt;always&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.1:53:53/udp&lt;span class="w"&gt; &lt;/span&gt;tonistiigi/dnsdock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Je vous passe ici le fonctionnement du résolveur mais si cela vous intéresse n'hésitez pas à le demander en commentaire.&lt;/p&gt;
&lt;p&gt;Récupérons l'adresse IP de notre dnsdock :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;inspect&lt;span class="w"&gt; &lt;/span&gt;--format&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'{{ .NetworkSettings.IPAddress }}'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dnsdock
&lt;span class="m"&gt;172&lt;/span&gt;.18.0.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On voit que le conteneur démarre bien sur notre réseau en 18 et non en 17.
Voyons si on peut le pinger :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ping&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.2
PING&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.2&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;56&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;data&lt;span class="w"&gt; &lt;/span&gt;bytes
Request&lt;span class="w"&gt; &lt;/span&gt;timeout&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;icmp_seq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notre hôte ne sait pas comment router ces paquets vers nos conteneurs. C'est normal, notre table de routage ne comprend pas de route pour. Nous allons donc l'ajouter en demandant au système de passer par la VM lorsqu'il veut acheminer des paquets vers les IP en &lt;code&gt;172.18.x.y&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /sbin/route -n add -net 172.18.0.0 -netmask 255.255.0.0 -gateway $(docker-machine ip dev)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Attention cette route n'est pas persistée lorsque vous redémarrez votre machine. Pour faire en sorte qu'elle soit appliquée à chaque redémarrage je vous conseille d'utiliser un fichier &lt;code&gt;plist&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version='1.0' encoding='UTF-8'?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;'1.0'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.docker.route&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;bash&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;-c&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;/sbin/route&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;-net&lt;span class="w"&gt; &lt;/span&gt;172.18.0.0&lt;span class="w"&gt; &lt;/span&gt;-netmask&lt;span class="w"&gt; &lt;/span&gt;255.255.0.0&lt;span class="w"&gt; &lt;/span&gt;-gateway&lt;span class="w"&gt; &lt;/span&gt;$(docker-machine&lt;span class="w"&gt; &lt;/span&gt;ip&lt;span class="w"&gt; &lt;/span&gt;dev)&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;KeepAlive&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;false/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;RunAtLoad&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;LaunchOnlyOnce&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Placez-le dans &lt;code&gt;/Library/LaunchDaemons/com.docker.route.default.plist&lt;/code&gt;. On ne peut pas le placer dans notre &lt;code&gt;~/Library&lt;/code&gt; car la commande a besoin des droits du super utilisateur. Il ne vous reste qu'à l'activer :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# launchctl load /Library/LaunchDaemons/com.docker.route.default.plist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Attention ces fichiers plist ont comme pré-requis que votre VM soit démarrée. Personnellemente je préfère ne pas démarrer ma VM au démarrage de ma machine et ne les utilise donc pas.&lt;/p&gt;
&lt;p&gt;On va ensuite désactiver le filtrage des adresses IP sur cette interface réseau, de sorte à pouvoir échanger sur des IP 172.18.x.y et pas uniquement 192.168.64.z.&lt;/p&gt;
&lt;p&gt;Regardez quelle est la propriété &lt;code&gt;member&lt;/code&gt; de &lt;code&gt;ifconfig bridge100&lt;/code&gt;. Chez moi c'est &lt;code&gt;en6&lt;/code&gt;, si vous avez autre chose adaptez la commande suivante.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# ifconfig bridge100 -hostfilter en6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vous pouvez vous faire un fichier &lt;code&gt;plist&lt;/code&gt; pour cette commande également, en gardant à l'esprit que vous devrez aussi automatiser le démarrage de la VM au lancement de votre machine.&lt;/p&gt;
&lt;p&gt;Vous devriez maintenant pouvoir pinger vos conteneurs :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ping&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.2
PING&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.2&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;56&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;data&lt;span class="w"&gt; &lt;/span&gt;bytes
&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.2:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;63&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.329&lt;span class="w"&gt; &lt;/span&gt;ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Relançons notre Nginx en lui donnant un alias DNS de sorte à ce que dnsdock puisse faire le mapping entre celui-ci et l'IP du conteneur nouvellement lancé :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DNSDOCK_ALIAS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;www.synbioz.dev&lt;span class="w"&gt; &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Voyons si notre résolveur sait nous retourner l'IP de notre conteneur :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;dig&lt;span class="w"&gt; &lt;/span&gt;@172.18.0.1&lt;span class="w"&gt; &lt;/span&gt;www.synbioz.dev

&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;DiG&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.8.3-P1&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;@172.18.0.1&lt;span class="w"&gt; &lt;/span&gt;www.synbioz.dev
&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;global&lt;span class="w"&gt; &lt;/span&gt;options:&lt;span class="w"&gt; &lt;/span&gt;+cmd
&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Got&lt;span class="w"&gt; &lt;/span&gt;answer:
&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt;&amp;gt;HEADER&lt;span class="s"&gt;&amp;lt;&amp;lt;- opco&lt;/span&gt;de:&lt;span class="w"&gt; &lt;/span&gt;QUERY,&lt;span class="w"&gt; &lt;/span&gt;status:&lt;span class="w"&gt; &lt;/span&gt;NOERROR,&lt;span class="w"&gt; &lt;/span&gt;id:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6337&lt;/span&gt;
&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;flags:&lt;span class="w"&gt; &lt;/span&gt;qr&lt;span class="w"&gt; &lt;/span&gt;rd&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;QUERY:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;ANSWER:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;AUTHORITY:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;ADDITIONAL:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WARNING:&lt;span class="w"&gt; &lt;/span&gt;recursion&lt;span class="w"&gt; &lt;/span&gt;requested&lt;span class="w"&gt; &lt;/span&gt;but&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;available

&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;QUESTION&lt;span class="w"&gt; &lt;/span&gt;SECTION:
&lt;span class="p"&gt;;&lt;/span&gt;www.synbioz.dev.&lt;span class="w"&gt;  &lt;/span&gt;IN&lt;span class="w"&gt; &lt;/span&gt;A

&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ANSWER&lt;span class="w"&gt; &lt;/span&gt;SECTION:
www.synbioz.dev.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;IN&lt;span class="w"&gt; &lt;/span&gt;A&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.3

&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Query&lt;span class="w"&gt; &lt;/span&gt;time:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;msec
&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SERVER:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.1#53&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.18.0.1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WHEN:&lt;span class="w"&gt; &lt;/span&gt;Wed&lt;span class="w"&gt; &lt;/span&gt;Apr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;:53:40&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2017&lt;/span&gt;
&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;MSG&lt;span class="w"&gt; &lt;/span&gt;SIZE&lt;span class="w"&gt;  &lt;/span&gt;rcvd:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Visiblement tout fonctionne bien. Il ne reste plus qu'à indiquer à notre système d'utiliser ce résolveur pour le TLD &lt;code&gt;.dev&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# echo "nameserver 172.18.0.1" &amp;gt; /etc/resolver/dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;C'est terminé, vous voilà prêt à faire tourner vos applications dans votre VM avec de biens meilleures performances.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170412/synbioz.dev.png"&gt;&lt;img alt="" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170412/synbioz.dev_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Compte rendu de l'apéro Ruby Nord d'avril</title>
    <id>https://martin.catty.fr/blog/compte-rendu-de-lapero-ruby-nord-davril/</id>
    <link href="https://martin.catty.fr/blog/compte-rendu-de-lapero-ruby-nord-davril/" rel="alternate"/>
    <updated>2017-04-11T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Cela faisait quelques temps que je n'étais pas allé à un apéro ruby et je n'ai pas été déçu. 3 présentations au programme que je vous détaille tout de suite.&lt;/p&gt;
&lt;h3&gt;Rails avec Docker&lt;/h3&gt;
&lt;p&gt;En premier lieu &lt;a href="https://twitter.com/yannirbah"&gt;Yann&lt;/a&gt; nous a montré comment faire tourner Rails avec Docker. Étant donné que l'on utilise Docker depuis mi 2015 chez Synbioz et qu'on l'a systématisé l'an passé sur tous nos projets (rails ou pas) j'avoue que je pensais m'ennuyer un peu.&lt;/p&gt;
&lt;p&gt;J'avais tout faux, Yann a su rendre ça très intéressant et concret, en partant d'une &lt;em&gt;codebase&lt;/em&gt; classique qu'il a progressivement «dockerisée». J'y ai au passage découvert &lt;a href="https://github.com/codekitchen/dinghy"&gt;dinghy&lt;/a&gt; qui facilite la création d'une machine virtuelle utilisant l'hyperviseur de base d'OSX (en lieu et place de VirtualBox), couplé à un partage NFS pour les volumes.&lt;/p&gt;
&lt;p&gt;Au final je n'ai pas gardé dinghy mais je me suis repenché sur Xhyve (qui possède un &lt;a href="https://github.com/zchee/docker-machine-driver-xhyve"&gt;driver docker-machine&lt;/a&gt; et le partage NFS en experimental). Les performances sont très prometteuses et l'usage de NFS permet de propager les événements du système de fichiers hôte vers le conteneur, ce qui fonctionne mal à travers VirtualBox et oblige à lancer les serveurs avec des commandes de polling.&lt;/p&gt;
&lt;p&gt;Au delà de conteneuriser une application en développement uniquement, Yann est allé jusqu'à la production et nous a présenté à l'occasion &lt;a href="https://kontena.io"&gt;Kontena&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il s'agit là d'une plateforme qui va permettre de gérer toute la chaine classique des conteneurs : stockage (registre), gestion des &lt;em&gt;stacks&lt;/em&gt;, gestion des variables d'environnement, orchestration etc…&lt;/p&gt;
&lt;p&gt;Autre point appréciable, la plateforme est agnostique en terme d'IAAS car compatible avec Azure, AWS, Google Cloud et plein d'autres.&lt;/p&gt;
&lt;p&gt;Étant en plein dans le sujet en ce moment avec Rancher, cela tombait à point nommé pour le mettre en concurrence.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://ruby-nord-kontena.surge.sh/#/"&gt;Lien vers les slides de Yann&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Dégemification&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://twitter.com/bobmaerten"&gt;Bob&lt;/a&gt; quant à lui nous a parlé de ses déboires avec &lt;a href="https://github.com/drapergem/draper"&gt;Draper&lt;/a&gt; lors de sa migration d'une application vers Rails 5.&lt;/p&gt;
&lt;p&gt;En effet, il s'est retrouvé face à des erreurs un peu absconses avant de se rendre compte qu'elles venaient de Draper, une gem non compatible avec Rails 5.&lt;/p&gt;
&lt;p&gt;S'en est suivi l'éternelle question : est-ce que je bascule sur un &lt;em&gt;fork&lt;/em&gt;, est-ce que j'attends une éventuelle mise à jour ? Et ce d'autant plus que le code à modifier pour re-faire fonctionner la gem était vraiment limité.&lt;/p&gt;
&lt;p&gt;Puis avec un peu de recul plusieurs questions de fond apparaissent : comment s'assurer que ma gem sera maintenue dans le temps ? Y investir du temps et/ou de l'argent peut être une solution mais dans ce cas précis le mainteneur semblait juste avoir déserté, ce qui arrive plus souvent qu'on ne le pense.&lt;/p&gt;
&lt;p&gt;Vient donc la question naturelle suivante : est-ce que j'ai vraiment besoin de cette gem ? Fait-elle vraiment énormément de choses que je ne pourrais pas faire moi-même ?&lt;/p&gt;
&lt;p&gt;Bob a fini par répondre par la négative à cette question et a donc implémenté le &lt;em&gt;pattern decorator&lt;/em&gt; à sa sauce, en utilisant SimpleDelegator.&lt;/p&gt;
&lt;p&gt;En conclusion : gardez vos Gemfile les plus fins possibles et choisissez vos gem avec précaution.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://speakerdeck.com/bobmaerten/retour-sur-un-processus-de-de-gem-ification-sur-une-appli-rails"&gt;Lien vers les slides de Bob&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Rails et l'asset pipeline&lt;/h3&gt;
&lt;p&gt;Enfin &lt;a href="https://twitter.com/_flexbox"&gt;David&lt;/a&gt; nous a retracé le couple Rails / &lt;em&gt;assets&lt;/em&gt; (aka «je t'aime moi non plus») depuis la version 3 de Rails.&lt;/p&gt;
&lt;p&gt;De la logique de «Je mets tout dans &lt;code&gt;public/assets&lt;/code&gt; et basta» (pre Rails 3.1), en passant par les gems &lt;code&gt;rails-*&lt;/code&gt; qui ont du mal à suivre les cycles de mise à jour des paquets npm (sans compter que le projet rails-assets a failli mourir), le &lt;code&gt;vendor/assets&lt;/code&gt; et &lt;code&gt;lib/assets&lt;/code&gt; pour en arriver à &lt;a href="https://github.com/rails/sprockets"&gt;Sprockets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La présentation retrace tout ce que gère Sprockets (processeurs, transformeurs, compresseurs, directives, env, manifest, pipeline, etc).&lt;/p&gt;
&lt;p&gt;Pour simplifier le tout, Rails 5.1 utilisera Yarn et Webpack. David nous a donc présenté dans les grandes lignes, son fonctionnement et son intégration dans Rails.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://courses.davidl.fr/presentations/sprockets/#/"&gt;Lien vers les slides de David&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Au final un vrai bon cru Ruby Nord, merci à tous ceux qui l'ont rendu possible.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Dragonfly, Nginx et contenu partiel</title>
    <id>https://martin.catty.fr/blog/dragonfly-nginx-et-contenu-partiel/</id>
    <link href="https://martin.catty.fr/blog/dragonfly-nginx-et-contenu-partiel/" rel="alternate"/>
    <updated>2017-03-22T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;En utilisant Dragonfly nous nous sommes rendus compte qu'au moment de servir des vidéos pour Safari et Safari mobile depuis notre application Rails, cela ne fonctionnait pas.&lt;/p&gt;
&lt;p&gt;En creusant un peu il s'avère que Safari est assez exigeant puisqu'il impose un support du &lt;em&gt;byte-range&lt;/em&gt; de la part du serveur qui sert la vidéo.&lt;/p&gt;
&lt;p&gt;C'est à dire que le &lt;em&gt;player&lt;/em&gt; veut pouvoir demander une tranche de la vidéo uniquement, de sorte à gérer sa mise en cache et ne pas nécessairement tout télécharger d'un coup. Il demande donc un contenu partiel (code HTTP 206).&lt;/p&gt;
&lt;p&gt;Pour cela il envoie un entête &lt;code&gt;byte-range&lt;/code&gt; dans la requête et attend en retour un entête &lt;code&gt;Accept-Ranges&lt;/code&gt; si le serveur le supporte.&lt;/p&gt;
&lt;p&gt;Exemple :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;--verbose&lt;span class="w"&gt; &lt;/span&gt;--range&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;-99&lt;span class="w"&gt; &lt;/span&gt;https://example.com/video.mp4&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Comme notre serveur le supporte nous recevons ce type d'entête :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Accept-Ranges: bytes
Content-Range: bytes 0-99/55766047
Content-Type: video/mp4
Content-Length: 100
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;À qui la charge de renvoyer des réponses partielles ?&lt;/h3&gt;
&lt;p&gt;La première idée serait de faire en sorte que notre application Rails puisse produire des réponses partielles.&lt;/p&gt;
&lt;p&gt;En fouillant un peu il existe &lt;a href="https://github.com/markevans/dragonfly/issues/197"&gt;un ticket ouvert sur le projet Dragonfly&lt;/a&gt; accompagné d'un &lt;a href="https://gist.github.com/fritzsche/2758045"&gt;gist&lt;/a&gt; qui vise à introduire un nouveau &lt;em&gt;middleware&lt;/em&gt; en charge de produire un morceau de réponse depuis le fichier final.&lt;/p&gt;
&lt;p&gt;Je pense que c'est une mauvaise idée de s'appuyer sur ce code pour plusieurs raisons :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;parce que c'est mal de copier/coller des gist sortis de nul part et vieux de 5 ans&lt;/li&gt;
&lt;li&gt;ce code effectue une lecture bit à bit particulièrement lente et qui impose de travailler sur le fichier complet&lt;/li&gt;
&lt;li&gt;autant que faire se peut, on souhaite éviter que ça soit Ruby qui fasse ce traitement car il sera nécessairement (plus) lent&lt;/li&gt;
&lt;li&gt;Nginx sait le faire de base et sans doute infiniment mieux et plus vite&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une autre solution serait d'utiliser &lt;code&gt;Rack::File&lt;/code&gt; &lt;a href="https://github.com/rack/rack/blob/master/lib/rack/file.rb#L78"&gt;qui gère les entêtes de type range&lt;/a&gt; mais autant utiliser notre reverse proxy pour faire ce travail plus efficacement.&lt;/p&gt;
&lt;h3&gt;Faire en sorte que Nginx traite nos requêtes partielles&lt;/h3&gt;
&lt;p&gt;La première chose à faire est de demander à Nginx de prendre la main lorsque nous renvoyons des fichiers (&lt;code&gt;send_file&lt;/code&gt;) dans Rails de sorte à ce que le flux de requêtes soit géré ainsi :&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170322/dragonfly_nginx_contenu_partiel.png"&gt;&lt;img alt="Flux de la requête" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/20170322/dragonfly_nginx_contenu_partiel_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pour cela nous activons l'entête qui va bien :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ce paramètre peut être configuré soit par environnement dans le fichier correspondant, soit dans le fichier &lt;code&gt;config/application.rb&lt;/code&gt; pour l'ensemble des environnements. Attention toutefois à la précédence, car si vous mettez la configuration dans les deux fichiers c'est celle du fichier d'environnement qui prendra le pas.&lt;/p&gt;
&lt;p&gt;Une fois fait, lors des demandes de fichier, Rack enverra un entête à Nginx en lui demandant de charger le fichier et en joignant une réponse (&lt;em&gt;body&lt;/em&gt;) vide.&lt;/p&gt;
&lt;p&gt;Côté Rails, le travail est fini. Il a passé la main à Nginx et peut passer à la requête suivante.&lt;/p&gt;
&lt;p&gt;C'est bien gentil mais Nginx a besoin de savoir où trouver le fichier en question. Dans le cas d'une requête type &lt;code&gt;https://example.com/files/foo.zip&lt;/code&gt; le mapping est simple, la configuration de Nginx utilise le répertoire &lt;code&gt;public&lt;/code&gt; de Rails comme &lt;em&gt;root&lt;/em&gt; et il va donc le trouver sans configuration supplémentaire.&lt;/p&gt;
&lt;p&gt;Dans le cas de Dragonfly, on a des URL un peu plus complexes type &lt;code&gt;/media/W1siZiIsIjIwMTcvMDMvMDEvMTI/video.mp4?sha=ueiu&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Dragonfly utilise &lt;code&gt;Rack::Cache&lt;/code&gt; pour mettre en cache les réponses et les servir plus rapidement. Il faut donc que nous expliquions à Nginx comment servir ces requêtes.&lt;/p&gt;
&lt;p&gt;En premier lieu nous allons raffiner un peu la configuration de Rack::Cache pour définir où nous voulons stocker notre cache.&lt;/p&gt;
&lt;p&gt;Dans le fichier &lt;code&gt;application.rb&lt;/code&gt;, pour le généraliser à tous les environnements, ou dans &lt;code&gt;production.rb&lt;/code&gt; pour la production uniquement, nous remplaçons &lt;code&gt;config.action_dispatch.rack_cache = true&lt;/code&gt; par :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action_dispatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rack_cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;metastore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"file:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../../shared/cache/dragonfly/meta'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;entitystore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"file:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../../shared/cache/dragonfly/body'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ici nous sommes dans le cadre d'un déploiement Capistrano, on veut donc stocker le cache dans le dossier &lt;em&gt;shared&lt;/em&gt; pour qu'il persiste les différents déploiements (attention à purger le cache de temps à autre).&lt;/p&gt;
&lt;p&gt;Maintenant il faut que Nginx prenne connaissance de ces URL afin de pouvoir les servir. Dans sa configuration nous allons définir ces chemins comme internes :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location /chemin/complet/vers/dragonfly/body {
  internal;
  alias /chemin/complet/vers/dragonfly/body;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ici &lt;code&gt;location&lt;/code&gt; et &lt;code&gt;alias&lt;/code&gt; sont identiques mais, sans même parler de Rails, on pourrait faire en sorte que Nginx serve les fichiers depuis &lt;code&gt;/var/uploads&lt;/code&gt; lorsqu'il reçoit une requête sur &lt;code&gt;/files&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location /files {
  internal;
  alias /var/uploads;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans ce cas, si on demande &lt;code&gt;/files/foo.zip&lt;/code&gt; il servira &lt;code&gt;/var/uploads/foo.zip&lt;/code&gt;.
Dernière chose à faire côté Nginx, renvoyer un entête de &lt;em&gt;mapping&lt;/em&gt; des fichiers.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location {
  proxy_set_header X-Accel-Mapping /chemin/complet/vers/dragonfly/body=/chemin/complet/vers/dragonfly/body;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lorsque Rack va recevoir cet entête de &lt;em&gt;mapping&lt;/em&gt;, il va pouvoir en retour indiquer où se trouve le fichier à aller chercher (car l'URL d'entrée est toujours &lt;code&gt;/media/…&lt;/code&gt;) et demander à Nginx de le servir directement via l'entête 'X-Accel-Redirect'.&lt;/p&gt;
&lt;p&gt;En clair, il va lui dire &lt;code&gt;/media/zzz&lt;/code&gt; se trouve dans &lt;code&gt;/chemin/complet/vers/dragonfly/body/A1&lt;/code&gt; et Nginx pourra le servir lui même.&lt;/p&gt;
&lt;p&gt;J'espère que cet article vous aidera à servir des contenus partiels dans le cas d'une &lt;em&gt;stack&lt;/em&gt; Rails + Nginx ou tout du moins à délester votre serveur applicatif en confiant à votre &lt;em&gt;reverse proxy&lt;/em&gt; le soin de servir vos fichiers.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Vault en production</title>
    <id>https://martin.catty.fr/blog/vault-en-production/</id>
    <link href="https://martin.catty.fr/blog/vault-en-production/" rel="alternate"/>
    <updated>2017-03-02T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Un backend différent avec S3&lt;/h3&gt;
&lt;p&gt;À présent nous avons gardé notre coffre en mémoire. Si notre conteneur s'arrête notre coffre est perdu. Voyons une solution plus pérenne avec un backend S3 par exemple.&lt;/p&gt;
&lt;p&gt;L'objectif est donc que Vault aille écrire ses fichiers dans un bucket. L'utilisateur (ou système) final n'a pas besoin d'accéder à ce bucket.&lt;/p&gt;
&lt;p&gt;Le paramétrage est très simple puisque le backend S3 est intégré de base. Paramètrons le fichier &lt;code&gt;config.hcl&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;backend &amp;quot;s3&amp;quot; {
  bucket = &amp;quot;nom&amp;quot;
  access_key = &amp;quot;&amp;quot;
  secret_key = &amp;quot;&amp;quot;
  region = &amp;quot;&amp;quot;
}

listener &amp;quot;tcp&amp;quot; {
 address = &amp;quot;0.0.0.0:8200&amp;quot;
 tls_disable = 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Par défaut la région est us-east-1. Si votre bucket utilise cette région vous n'avez pas besoin de le préciser. Pour le reste il vous suffit de remplir les informations nécessaires.&lt;/p&gt;
&lt;p&gt;Toutefois vous pouvez utiliser des variables d'environnements plutôt que ces valeurs, solution qui a ma préférence.
Dans ce cas dans un fichier un plat, par exemple &lt;code&gt;.env&lt;/code&gt; on peut avoir :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AWS_ACCESS_KEY_ID=
AWS_DEFAULT_REGION=
AWS_S3_BUCKET=
AWS_SECRET_ACCESS_KEY=
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il nous reste à lancer notre conteneur :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run --rm --name vault-server --env-file .env --cap-add IPC_LOCK -v $(pwd):/vault vault server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vous pouvez maintenant lancer un client et l'utiliser exactement comme avant.&lt;/p&gt;
&lt;p&gt;J'ai passé quelques temps à comprendre pourquoi j'avais systématiquement une 403 sur mon bucket quand je lançais avec le backend s3 alors qu'hors docker je n'avais pas ce souci. Il s'avère que l'heure n'était pas correcte dans mon conteneur. J'ai pu m'en rendre compte en testant avec le cli aws dans ce dernier. Si vous utilisez une VM pour lancer vos conteneurs (notamment si vous utilisez boot2docker) assurez vous que celle ci est à l'heure. Si ce n'est pas le cas vous pouvez utiliser le fichier &lt;code&gt;/etc/localtime&lt;/code&gt; de votre hôte :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-machine scp /etc/localtime machine-name:/tmp/localtime
docker-machine ssh machine-name 'sudo mv /tmp/localtime /etc'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Les points de montage&lt;/h3&gt;
&lt;p&gt;Un peu plus haut je vous expliquais que nous utilisions le point de montage &lt;code&gt;secret&lt;/code&gt; par défaut. Voyons voir ce que sont ces points de montage.&lt;/p&gt;
&lt;p&gt;Par défaut nous avons quelques points de montage déjà disponibles :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/ # vault mounts
Path        Type       Default TTL  Max TTL  Description
cubbyhole/  cubbyhole  n/a          n/a      per-token private secret storage
secret/     generic    system       system   generic secret storage
sys/        system     n/a          n/a      system endpoints used for control, policy and debugging
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;À l'image d'un système de fichier les points de montage gèrent chacun les même opérations (lecture, écriture…) mais pas de la même façon. Par exemple un système de fichier peut être chiffré mais les opérations accessibles en entrée ou en sortie seront les mêmes. Pour les points de montage c'est la même chose.&lt;/p&gt;
&lt;p&gt;Par exemple un point de montage AWS ne va pas stocker les secrets de façon brute, à l'inverse du point de montage &lt;code&gt;generic&lt;/code&gt;.
Il faut garder en tête que les backend sont cloisonnés. Un backend ne peut pas aller lire les clés d'un autre backend et c'est également vrai pour les jetons «utilisateurs».&lt;/p&gt;
&lt;p&gt;Les points de montage peuvent donc être montés et démontés simplement :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vault mount aws
Successfully mounted 'aws' at 'aws'!
&lt;/code&gt;&lt;/pre&gt;</content>
  </entry>
  <entry>
    <title>Partageons nos secrets avec Vault</title>
    <id>https://martin.catty.fr/blog/partageons-nos-secrets-avec-vault/</id>
    <link href="https://martin.catty.fr/blog/partageons-nos-secrets-avec-vault/" rel="alternate"/>
    <updated>2017-02-24T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Combien de fois avez-vous pesté car vous n'aviez plus les &lt;em&gt;credentials&lt;/em&gt; pour vous connecter sur un environnement de &lt;em&gt;staging&lt;/em&gt; ou de production ?&lt;/p&gt;
&lt;p&gt;Vous importez un &lt;em&gt;dump&lt;/em&gt; en dev et vous vous dépêchez d'aller changer le mot de passe en console car vous ne connaissez pas celui que vous avez importé ?&lt;/p&gt;
&lt;p&gt;Ou vous lancez un conteneur sans être sûr qu'il ne manque pas des variables d'environnement ou que leurs valeurs sont à jour ?&lt;/p&gt;
&lt;p&gt;Pour répondre à ces problématiques, arrêtez de vous envoyer des mots de passe dans Slack, il existe &lt;a href="https://www.vaultproject.io/"&gt;Vault&lt;/a&gt;, maintenu par Hashicorp parmi leur excellente suite d'outils.&lt;/p&gt;
&lt;h3&gt;Pourquoi utiliser Vault ?&lt;/h3&gt;
&lt;p&gt;Vault vise à résoudre les problématiques suivantes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;partage d'informations sensibles de manière sécurisée&lt;/li&gt;
&lt;li&gt;contrôle d'accès avec possibilité de révoquer des accès&lt;/li&gt;
&lt;li&gt;granularité via des polices permettant de ne partager que certaines données d'un coffre&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Par ailleurs chez Synbioz nous avions quelques pré-requis avant de choisir un outil particulier :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pas de dépendance à un système de stockage particulier&lt;/li&gt;
&lt;li&gt;open source (non, le fait qu'un système soit fermé n'est pas garant de sécurité)&lt;/li&gt;
&lt;li&gt;utilisable en CLI (et donc automatisable)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans le déroulé nous allons donc voir tour à tour comment lire et écrire dans un coffre de façon basique, utiliser les jetons, intégrer PGP dans la boucle et enfin sceller et desceller un coffre.&lt;/p&gt;
&lt;p&gt;Dans un deuxième article nous verrons comment utiliser un &lt;em&gt;backend&lt;/em&gt; de stockage différent avec S3, des points de montage pour sécuriser l'accès à différents services, se servir de Vault pour provisionner des variables d'environnement utilisées dans un conteneur et enfin mettre son service Vault derrière un Nginx.&lt;/p&gt;
&lt;h3&gt;À la découverte de Vault&lt;/h3&gt;
&lt;p&gt;Voyons tout de suite un exemple d'utilisation. J'utilise Docker pour tester, mais vous pouvez sans doute l'installer avec votre package manager favori :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;vault-server&lt;span class="w"&gt; &lt;/span&gt;vault
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Par défaut Vault va démarrer en mode développement, c'est à dire qu'il sera accessible sur l'interface de &lt;em&gt;loopback&lt;/em&gt; (127.0.0.1), sans sécurité particulière (pas de TLS ni de jeton d'accès).&lt;/p&gt;
&lt;p&gt;Le log nous explique comment faire pour se connecter à Vault.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;RNING:&lt;span class="w"&gt; &lt;/span&gt;Dev&lt;span class="w"&gt; &lt;/span&gt;mode&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;enabled!

In&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;mode,&lt;span class="w"&gt; &lt;/span&gt;Vault&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;completely&lt;span class="w"&gt; &lt;/span&gt;in-memory&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;unsealed.
Vault&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;configured&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;only&lt;span class="w"&gt; &lt;/span&gt;have&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;single&lt;span class="w"&gt; &lt;/span&gt;unseal&lt;span class="w"&gt; &lt;/span&gt;key.&lt;span class="w"&gt; &lt;/span&gt;The&lt;span class="w"&gt; &lt;/span&gt;root
token&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;already&lt;span class="w"&gt; &lt;/span&gt;been&lt;span class="w"&gt; &lt;/span&gt;authenticated&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;CLI,&lt;span class="w"&gt; &lt;/span&gt;so&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;can
immediately&lt;span class="w"&gt; &lt;/span&gt;begin&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;Vault&lt;span class="w"&gt; &lt;/span&gt;CLI.

The&lt;span class="w"&gt; &lt;/span&gt;only&lt;span class="w"&gt; &lt;/span&gt;step&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;need&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;take&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;following
environment&lt;span class="w"&gt; &lt;/span&gt;variables:

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VAULT_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'http://0.0.0.0:8200'&lt;/span&gt;

The&lt;span class="w"&gt; &lt;/span&gt;unseal&lt;span class="w"&gt; &lt;/span&gt;key&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;token&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;reproduced&lt;span class="w"&gt; &lt;/span&gt;below&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you
want&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;seal/unseal&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;Vault&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;play&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;authentication.

Unseal&lt;span class="w"&gt; &lt;/span&gt;Key:&lt;span class="w"&gt; &lt;/span&gt;VJconzd1Ogx60tDSFZPlb0VW0J5Ekh+GmqtdJ0vC/jM&lt;span class="o"&gt;=&lt;/span&gt;
Root&lt;span class="w"&gt; &lt;/span&gt;Token:&lt;span class="w"&gt; &lt;/span&gt;145a0045-b35e-8624-fa0c-aef810667f34
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On va maintenant lancer un client en passant en variable d'environnement l'URL et le jeton :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--link&lt;span class="w"&gt; &lt;/span&gt;vault-server&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VAULT_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://vault-server:8200&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;145a0045-b35e-8624-fa0c-aef810667f34&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;vault&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si vous n'utilisez pas docker en développement vous aurez juste à exporter &lt;code&gt;VAULT_ADDR&lt;/code&gt; dans un shell différent comme indiqué au démarrage du serveur.&lt;/p&gt;
&lt;p&gt;On peut maintenant commencer à écrire un secret :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;secret/hello&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Et consulter nos secrets :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# vault read secret/hello&lt;/span&gt;
Key&lt;span class="w"&gt;               &lt;/span&gt;Value
---&lt;span class="w"&gt;               &lt;/span&gt;-----
refresh_interval&lt;span class="w"&gt;  &lt;/span&gt;768h0m0s
value&lt;span class="w"&gt;             &lt;/span&gt;world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ici &lt;code&gt;secret&lt;/code&gt; représente le point de montage par défaut, nous sommes obligés d'utiliser ce préfixe (nous y reviendrons dans le deuxième article). Pour le reste nous pouvons utiliser le nombre de sous niveaux que nous voulons. Dans notre cas nous utilisons une arborescence de type client/projet/environnement.&lt;/p&gt;
&lt;p&gt;Rien ne nous empêche d'écrire plusieurs clés / valeurs d'un coup. Attention toutefois, si vous écrivez &lt;code&gt;foo=bar&lt;/code&gt; puis &lt;code&gt;bar=baz&lt;/code&gt;, la deuxième instruction va tout écraser, il faut donc utiliser soit des &lt;em&gt;namespaces&lt;/em&gt; différents soit repasser l'ensemble des clés / valeurs.&lt;/p&gt;
&lt;p&gt;Vault, quand il est lancé en tant que serveur, est accessible via une API au travers d'HTTP.
Dans notre conteneur client, on pourrait très bien faire :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;GET&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"X-Vault-Token:&lt;/span&gt;&lt;span class="nv"&gt;$VAULT_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://vault-server:8200/v1/secret/hello
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"request_id"&lt;/span&gt;:&lt;span class="s2"&gt;"929e52b1-dcda-6082-702a-2a17e7faa292"&lt;/span&gt;,&lt;span class="s2"&gt;"lease_id"&lt;/span&gt;:&lt;span class="s2"&gt;""&lt;/span&gt;,&lt;span class="s2"&gt;"renewable"&lt;/span&gt;:false,&lt;span class="s2"&gt;"lease_duration"&lt;/span&gt;:2764800,&lt;span class="s2"&gt;"data"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"value"&lt;/span&gt;:&lt;span class="s2"&gt;"world"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"wrap_info"&lt;/span&gt;:null,&lt;span class="s2"&gt;"warnings"&lt;/span&gt;:null,&lt;span class="s2"&gt;"auth"&lt;/span&gt;:null&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;En dehors du mode développement&lt;/h3&gt;
&lt;p&gt;Dans notre exemple précédent nous avons utilisé la clé root pour lire et écrire dans le coffre. C'est clairement une pratique à proscrire. La clé root doit uniquement servir à gérer la configuration du coffre.&lt;/p&gt;
&lt;p&gt;Sortons un peu du mode développement pour explorer le fonctionnement de Vault. Vault fonctionne avec des fichiers de configuration HCL, qui est une syntaxe de plus haut niveau que JSON (c'est à dire que des fichiers JSON sont lisibles par un parseur HCL, l'inverse n'est pas vrai).&lt;/p&gt;
&lt;p&gt;Voici notre fichier &lt;code&gt;config&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;"inmem"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;listener&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0:8200"&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;tls_disable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous relançons notre serveur en conteneur :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;vault-server&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;:/vault&lt;span class="w"&gt; &lt;/span&gt;vault&lt;span class="w"&gt; &lt;/span&gt;server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notre fichier de configuration viendra donc prendre la place &lt;code&gt;/vault/config&lt;/code&gt; dans notre conteneur pour surcharger le fichier par défaut.&lt;/p&gt;
&lt;p&gt;Pour le moment nous restons sur le backend &lt;code&gt;inmem&lt;/code&gt; pour &lt;em&gt;in memory&lt;/em&gt;. On écoute sur &lt;code&gt;0.0.0.0&lt;/code&gt; plutôt que l'interface de &lt;em&gt;loopback&lt;/em&gt; car on accédera à notre conteneur depuis un autre conteneur. Si vous testez localement, &lt;code&gt;127.0.0.1&lt;/code&gt; est suffisant.&lt;/p&gt;
&lt;p&gt;Si nous lançons notre conteneur de cette façon nous allons tomber sur cette erreur : Error initializing core: Failed to lock memory: cannot allocate memory.&lt;/p&gt;
&lt;p&gt;Par défaut mlock est activé et permet d'empêcher l'écriture de la mémoire dans la &lt;em&gt;swap&lt;/em&gt;. La première solution est de désactiver mlock dans notre fichier de configuration &lt;code&gt;disable_mlock = true&lt;/code&gt;. C'est acceptable en développement mais pas en production.&lt;/p&gt;
&lt;p&gt;Nous allons donc donner quelques pouvoirs en plus à notre conteneur avec &lt;code&gt;-cap-add&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;vault-server&lt;span class="w"&gt; &lt;/span&gt;--cap-add&lt;span class="w"&gt; &lt;/span&gt;IPC_LOCK&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;:/vault&lt;span class="w"&gt; &lt;/span&gt;vault&lt;span class="w"&gt; &lt;/span&gt;server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous avons également désactivé TLS, ce que nous ne ferions pas en environnement de production, notre server Vault ne doit pas être accessible sans certificat.&lt;/p&gt;
&lt;p&gt;Connectons-nous avec notre conteneur client :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--link&lt;span class="w"&gt; &lt;/span&gt;vault-server&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VAULT_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://vault-server:8200&lt;span class="w"&gt; &lt;/span&gt;vault&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Étant donné que notre serveur n'est pas lancé avec l'option &lt;code&gt;-dev&lt;/code&gt;, le coffre n'est pas initialisé.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vault va nous générer un certain nombre de clés cette fois, en plus de la clé maîtresse (&lt;em&gt;initial root token&lt;/em&gt;) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Unseal&lt;span class="w"&gt; &lt;/span&gt;Key&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;dF42haxHtfRsFQH7FSwKEYW+u2988+pETp20GX1wZK4B
Unseal&lt;span class="w"&gt; &lt;/span&gt;Key&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;qlkaWcv1+uwlMLRFsxXIIZ8HdcNo2R8YchRkE+Ft2u8C
Unseal&lt;span class="w"&gt; &lt;/span&gt;Key&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;gsF8DlJJRSH7CBnQsudrv/a/F4opmYfzM2Ee5VWxLgYD
Unseal&lt;span class="w"&gt; &lt;/span&gt;Key&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;BQXqS6NIk8NHuONQ44Ob2ri3ITDm4qspJFPiIO6m1pME
Unseal&lt;span class="w"&gt; &lt;/span&gt;Key&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;LZ2MHDr0LA6ZgE7F4nE4RNEPQ3mnojPCZSaY1lp6InoF
Initial&lt;span class="w"&gt; &lt;/span&gt;Root&lt;span class="w"&gt; &lt;/span&gt;Token:&lt;span class="w"&gt; &lt;/span&gt;8733cbf6-679d-9ebd-a207-f67c560ded2a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ces clés permettront de desceller le coffre. La protection du coffre repose sur un partage des clés, selon l'algorithme de &lt;a href="https://en.wikipedia.org/wiki/Shamir's_Secret_Sharing"&gt;Shamir&lt;/a&gt; ; c'est à dire qu'il est nécessaire de posséder un certain nombre de clés pour accéder au coffre.&lt;/p&gt;
&lt;p&gt;Par défaut Vault est configuré de manière à ce qu'il faille 3 clés sur les 5, mais il est possible de changer ces deux chiffres via la configuration. Nous en verrons un exemple un peu plus bas.&lt;/p&gt;
&lt;p&gt;Si vous êtes paranoïaque ou curieux quant à cet algorithme, sachez que son auteur n'est autre que le co-inventeur de [RSA](&lt;a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)"&gt;https://en.wikipedia.org/wiki/RSA_(cryptosystem)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rappel&lt;/strong&gt; : la clé maîtresse a vocation à protéger ces clés-ci, pas à accéder directement au coffre. Cette clé est en fait attachée à la &lt;em&gt;policy root&lt;/em&gt;, la police principale, qui a tous les droits.&lt;/p&gt;
&lt;p&gt;Ces clés elles-même servent à récupérer l'accès au coffre, par exemple s'il a été scellé (manuellement ou si Vault redémarre)&lt;/p&gt;
&lt;p&gt;Pour générer un jeton d'accès nous allons donc nous connecter avec notre jeton &lt;em&gt;root&lt;/em&gt; via :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;auth
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On se connecte avec le jeton root et on lance la création d'un jeton via :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;token-create
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Qui nous donne :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Key&lt;span class="w"&gt;              &lt;/span&gt;Value
---&lt;span class="w"&gt;              &lt;/span&gt;-----
token&lt;span class="w"&gt;            &lt;/span&gt;1a792fec-18c5-63fa-fea6-ff5801cc3227
token_accessor&lt;span class="w"&gt;   &lt;/span&gt;95c9ec10-207a-bd14-8633-ab31136b579a
token_duration&lt;span class="w"&gt;   &lt;/span&gt;0s
token_renewable&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
token_policies&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;root&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vous avez compris les grands principes concernant la lecture, l'écriture et la protection du coffre. Allons maintenant un peu plus loin pour faire en sorte que l'utilisateur A puisse accéder au coffre Synbioz, le B au coffre client et le C au deux.&lt;/p&gt;
&lt;p&gt;Pour cela il nous faut mettre en place des polices d'accès. Ces polices sont gérées comme le reste via des fichiers HCL. Dans &lt;code&gt;/vault&lt;/code&gt; nous créons le fichier &lt;code&gt;synbioz.hcl&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deny"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret/synbioz/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Et &lt;code&gt;client.hcl&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deny"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret/client/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notre stratégie est celle de la liste blanche, nous bloquons l'accès à toutes les ressources par défaut, pour définir ensuite lesquelles sont accessibles.&lt;/p&gt;
&lt;p&gt;À noter que les polices peuvent être ajoutées à chaud via un client connecté avec le jeton &lt;em&gt;root&lt;/em&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;policy-write&lt;span class="w"&gt; &lt;/span&gt;synbioz&lt;span class="w"&gt; &lt;/span&gt;synbioz.hcl
vault&lt;span class="w"&gt; &lt;/span&gt;policy-write&lt;span class="w"&gt; &lt;/span&gt;client&lt;span class="w"&gt; &lt;/span&gt;client.hcl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous pouvons maintenant créer un jeton d'accès avec ces polices :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;token-create&lt;span class="w"&gt; &lt;/span&gt;-policy&lt;span class="w"&gt; &lt;/span&gt;synbioz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ce qui nous donne:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Key&lt;span class="w"&gt;              &lt;/span&gt;Value
---&lt;span class="w"&gt;              &lt;/span&gt;-----
token&lt;span class="w"&gt;            &lt;/span&gt;1d78e687-ec82-fcd3-6b9a-47c8446fc3b6
token_accessor&lt;span class="w"&gt;   &lt;/span&gt;59fa5cd9-90c9-719c-3be7-7d5a4dbe7cc0
token_duration&lt;span class="w"&gt;   &lt;/span&gt;768h0m0s
token_renewable&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
token_policies&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;synbioz&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous sommes prêts à écrire dans le coffre Synbioz :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;secret/synbioz/website/production&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Et à lire :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;secret/synbioz/website/production
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le format par défaut est de type table :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Key&lt;span class="w"&gt;               &lt;/span&gt;Value
---&lt;span class="w"&gt;               &lt;/span&gt;-----
refresh_interval&lt;span class="w"&gt;  &lt;/span&gt;768h0m0s
foo&lt;span class="w"&gt;               &lt;/span&gt;bar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Mais on peut également utiliser &lt;code&gt;json&lt;/code&gt; ou &lt;code&gt;yaml&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;json&lt;span class="w"&gt; &lt;/span&gt;secret/synbioz/website/production
&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"request_id"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3fc38113-d333-9df3-5132-87110a1e3465"&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"lease_id"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"lease_duration"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2764800&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"renewable"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;false,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"warnings"&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;null
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Par contre si j'essaie de lire les informations du &lt;em&gt;namespace&lt;/em&gt; client :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;secret/client/website/production
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Error&lt;span class="w"&gt; &lt;/span&gt;reading&lt;span class="w"&gt; &lt;/span&gt;secret/client/website/production:&lt;span class="w"&gt; &lt;/span&gt;Error&lt;span class="w"&gt; &lt;/span&gt;making&lt;span class="w"&gt; &lt;/span&gt;API&lt;span class="w"&gt; &lt;/span&gt;request.

URL:&lt;span class="w"&gt; &lt;/span&gt;GET&lt;span class="w"&gt; &lt;/span&gt;http://vault-server:8200/v1/secret/client/website/production
Code:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;403&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;Errors:

*&lt;span class="w"&gt; &lt;/span&gt;permission&lt;span class="w"&gt; &lt;/span&gt;denied
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour créer un jeton d'accès ayant plusieurs polices, il suffit de répéter le paramètre &lt;code&gt;policy&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;token-create&lt;span class="w"&gt; &lt;/span&gt;-policy&lt;span class="w"&gt; &lt;/span&gt;client&lt;span class="w"&gt; &lt;/span&gt;-policy&lt;span class="w"&gt; &lt;/span&gt;synbioz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dans ce cas notre jeton aura les droits des deux polices. Les polices peuvent parfaitement être modifiées à chaud. L'effet des modifications est immédiat.&lt;/p&gt;
&lt;p&gt;Évidemment on peut à tout instant révoquer un jeton via :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;token-revoke&lt;span class="w"&gt; &lt;/span&gt;token
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Sécuriser ses jetons avec PGP&lt;/h3&gt;
&lt;p&gt;Pour aller un cran plus loin en terme de sécurité on peut faire en sorte de chiffrer les jetons avec des clés PGP. Vault sait s'interfacer directement avec le service &lt;a href="https://keybase.io"&gt;Keybase&lt;/a&gt;, que nous utiliserons dans l'exemple ci-dessous. Toutefois il est tout à fait possible d'utiliser directement le &lt;a href="https://www.vaultproject.io/docs/concepts/pgp-gpg-keybase.html"&gt;client GPG&lt;/a&gt;. À l'initialisation du coffre :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;vault&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;-key-shares&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-key-threshold&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-pgp-keys&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"keybase:fuse,keybase:fuse,keybase:fuse"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ici on va donc définir que le coffre est protégé par 3 clés et qu'il en faut au moins 2 pour le déverrouiller. Mon exemple n'est pas très intéressant car les 3 jetons seront chiffrés avec ma clé publique. L'intérêt est de chiffrer ces clés avec les clés de personnes différentes pour faire en sorte qu'un quorum soit nécessaire pour sceller / desceller le coffre.&lt;/p&gt;
&lt;p&gt;On récupère la même chose qu'auparavant excepté que les clés sont chiffrées :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Unseal Key 1: wcBMA3BSIcJ3gMZmAQgACglV/1XRqo556XEwhA1q5tRzFo89d4kSuWOZpYU+KS8eyGMdYI+Uzw3+Ts9pxrCpg7VfDPQSlK39qys4QPH6jasqv/uR7LNYOjddDw8gQLxYc1m3VQP+4Gvz94yNEeLz0h1AcKoBoJZO3kmoLiqao6MYlMLfB6RlurY5A441UtqrfmtTU+l6vSsL8PO+t2UXw4HwFQSEAaBiX7cVLvUvsPk2B2RNdVb9JB/TyUilRpHe8cV6ijV1yZlr5oqMR9vR1L3cN03c9SGgoqj/we1VG5d36ucsCui90H3gEWH2snMfWKh8TXR8xX04QbP2ssmUcj2WzK73iuBFmqcSpDcPHtLgAeRRbXvR3p4HfceZcsaD+SED4cIS4AbgJOEDieAQ4jIgFb3geebkBw1Oxq+D8pJl03zToGrYIoUfFSXTQbPt8gqBBTbHsbtDERbYS3f1n/hwhgHiboX9XJ04WNMkAP1QiHk2xZqe4Lvh06zgvOT5WbGEGf2WeumSOBkcwEMj4u5TQUvhJqwA
Unseal Key 2: wcBMA3BSIcJ3gMZmAQgAuMcRamfiG4cgdsuhkSjipWgyk0z3zDYlbflzOB2T9YwN6+1LhoPcK26N/RHwNsM/FDI67WYYGEhohDkisdBqhBQt7TCogsJjIoV0EpUiiqowxJH/rEr6AUQdgMSt5NbabzMypHyFz3SXKGijmZLqW66lfQ3aY9Hc1M4H/p/2BLQA9elI39UJbIElIP6NnNYGy7CDD/s6jyHeVSoqmC1E1ma6GyaQFEDUDhUzSbacW/dPjIqDcA8DIlHJDJKjZ4u7tKXQkr2dI2DWEpckc/i7W5+29c4PYG7P8+nS+xwV/1ydOJ+DLV6XJDGyOS8tBaLYOcIUY0d1S5jXtP8Sqf9ELdLgAeS2sm1ppwUrtkdZZJz46E+u4W3F4O/gF+F1PeA54qsbk7PgR+YcVdEa831YEzGcotCb7scie+lHLx5vCZPT5oRD2kBgUlXGctU64DfaoeTDska9Pq29e6Yb5h0a3CiqIWwbO7s24BjhtyXgseSsPs+1xPzP0sYD93Gateq54s1D4aTh3FgA
Unseal Key 3: wcBMA3BSIcJ3gMZmAQgA6MnGDWZMcUk9s9pxu5/cmE1DxFyKeAjJ54UApsPQy3IEWe72N8bFx0jzXYNQ13XZxfZmsQRDaVYBvcwTtUs29BP8OGb0RXJsFSJUzRW7bemA5DvWd6o3SgKKCYs1Qt65b9Ju5dJs4YZgjwXYttHtqT037EzRPIWgyHNyM+UlNe8JWk7SjPSXgtP0kuRY99cU8Ef/zkVXzuY++AvfgIEy3oAB5Js91p6McqMHHu7UTU+ouGd47UvIstWcUoxklgwcugT3hwjxzeWREdmSMSf8VufI1Kt7/5ZfeYBdX/OQnXn6te++p0EBq0bRswvTTxMonHrTZcV4Hcll20eQH2lOJtLgAeTR6TtdEaMXf5YqPf8WT4Su4YEy4HDg3eF6SuC94o5zmv7g8OahpuuvGeiNqNfVSL8uEc67kVaKXTGc6r08oNn4GDDo/NE/JAeYzv1RG5M1Z7u4HMFtsw93GgthjqDTW7nD8lji4PvhJMjgyuQUfdkOiwryYccVEiPGceBE4ppPkIvhtncA
Initial Root Token: b565ce84-b273-8644-76a5-ded5b9ff4a66
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mettons que l'on stocke quelques clés dans notre coffre puis que l'on stoppe notre serveur avant de le relancer.&lt;/p&gt;
&lt;p&gt;Au moment où l'on va vouloir s'authentifier avec &lt;code&gt;vault auth&lt;/code&gt; on va tomber sur cette erreur :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Code: 503. Errors:

* Vault is sealed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Le processus ayant été arrêté puis relancé, le coffre est scellé. Si le processus est arrêté un peu sauvagement le coffre ne sera pas corrompu, il sera scellé également.&lt;/p&gt;
&lt;p&gt;Avant de pouvoir l'utiliser de nouveau nous devons donc le desceller avec au moins 2 jetons. En premier lieu il nous faut déchiffrer nos jetons :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wcBMA3BSI…"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;keybase&lt;span class="w"&gt; &lt;/span&gt;pgp&lt;span class="w"&gt; &lt;/span&gt;decrypt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Une fois la clé récupérée on commence à desceller le coffre :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vault unseal
Key (will be hidden):
Sealed: true
Key Shares: 3
Key Threshold: 2
Unseal Progress: 1
Unseal Nonce: 1a22bfb2-c3ad-c855-27e9-bc3d5c7ee066
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On voit ici que le processus s'est bien passé et qu'il nous reste à refaire l'opération une fois (&lt;em&gt;unseal progress&lt;/em&gt;). On répète l'opération avec la deuxième clé.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vault unseal
Key (will be hidden):
Sealed: false
Key Shares: 3
Key Threshold: 2
Unseal Progress: 0
Unseal Nonce:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Le coffre n'est plus scellé à présent. On peut s'authentifier et l'utiliser normalement.&lt;/p&gt;
&lt;p&gt;J'espère que ce premier article vous aura convaincu de l'intérêt de Vault, que ce soit pour l'utiliser pour partager des accès à des ressources ou pour le mettre en place dans une chaine de déploiement plus poussée.&lt;/p&gt;
&lt;p&gt;Dans notre prochain article nous irons un peu plus loin pour se rapprocher de ce deuxième cas d'usage.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Capital à la rencontre de Synbioz</title>
    <id>https://martin.catty.fr/blog/capital-a-la-rencontre-de-synbioz/</id>
    <link href="https://martin.catty.fr/blog/capital-a-la-rencontre-de-synbioz/" rel="alternate"/>
    <updated>2016-12-01T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;En cette fraiche fin d'année Capital est venu nous rendre visite armé de ses caméras :&lt;/p&gt;
&lt;iframe src="https://player.vimeo.com/video/192603143" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;a href="https://vimeo.com/192603143"&gt;Capital &amp;agrave; la rencontre de Synbioz&lt;/a&gt; via &lt;a href="https://vimeo.com/synbioz"&gt;synbioz&lt;/a&gt; sur &lt;a href="https://vimeo.com"&gt;Vimeo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Si vous désirez en savoir plus sur nos références, nos méthodes de travail et nos technologies, rendez vous dans la section &lt;a href="/contact?o=capital"&gt;contact&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Un peu de magie avec to_proc</title>
    <id>https://martin.catty.fr/blog/un-peu-de-magie-avec-to_proc/</id>
    <link href="https://martin.catty.fr/blog/un-peu-de-magie-avec-to_proc/" rel="alternate"/>
    <updated>2016-01-13T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Vous le savez, ici on aime bien démystifier les comportements pseudo magiques que nous offrent ruby et rails.&lt;/p&gt;
&lt;p&gt;D'ailleurs il est parfois difficile de savoir si un bout de code vient du langage ou du framework.&lt;/p&gt;
&lt;p&gt;À tel point qu'on a des quiz dédiés pour tenter de deviner &lt;a href="http://railshurts.com/quiz/"&gt;quand il s'agit de ruby ou rails&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Alors, ce snippet, et notamment &lt;code&gt;&amp;amp;:&lt;/code&gt;, ruby ou rails ?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les deux mon général !&lt;/p&gt;
&lt;h3&gt;Remontons le temps&lt;/h3&gt;
&lt;p&gt;Et oui une fois n'est pas coutume, le &lt;code&gt;&amp;amp;:&lt;/code&gt; nous vient de Ruby on Rails mais a été ré-intégré directement dans le langage, à partir de la version 1.8.7.&lt;/p&gt;
&lt;p&gt;On en oublierait presque que notre chère version 1.8.6 aurait renvoyé un &lt;code&gt;TypeError: wrong argument type Symbol (expected Proc)&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Comment ça marche ?&lt;/h3&gt;
&lt;p&gt;Soyons clair, même en 1.8.6, il était possible d'invoquer un bloc de code avec &lt;code&gt;&amp;amp;&lt;/code&gt;, simplement on ne pouvait pas le faire sur un symbole et donc pas par le biais de &lt;code&gt;&amp;amp;:&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Par exemple, ce code fonctionne parfaitement, même en 1.8.6.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Hello&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_proc&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="no"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;À partir du moment ou un objet est «procable» (si vous avez une version FR je prends en commentaire) on peut l'invoquer de la sorte. Et pour le rendre procable, il suffit de définir &lt;code&gt;to_proc&lt;/code&gt; et le comportement associé.&lt;/p&gt;
&lt;p&gt;L'avantage du &lt;code&gt;to_proc&lt;/code&gt;, c'est qu'il transmet automatiquement son contexte :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Hello&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_proc&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello from &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="no"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;Hello&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Les deux dernières instructions font la même chose, mais avouez que la première est plus élégante. Dans tous les cas le contenu de notre itérateur est automatiquement passé à la proc.&lt;/p&gt;
&lt;p&gt;Ce qui veut dire qu'en rendant un élément de base procable on pourrait très bien appeler dynamiquement des méthodes sur les éléments de l'itérateur.&lt;/p&gt;
&lt;p&gt;Imaginons que nous aimerions pouvoir faire ceci :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="s1"&gt;'to_s'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Il suffit de rendre nos string propres. Euh, rendre nos String proc(able).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;to_proc&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# dans notre appel self sera donc 'to_s'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Voilà, c'est tout ce dont nous avons besoin. Le choix a été fait de rendre procable les symboles plutôt que les String (et c'est mieux ainsi), mais le fonctionnement est strictement identique.&lt;/p&gt;
&lt;h3&gt;Même les Hash s'y mettent&lt;/h3&gt;
&lt;p&gt;Dans la récente release de &lt;a href="https://www.ruby-lang.org/fr/news/2015/12/25/ruby-2-3-0-released/"&gt;Ruby 2.3&lt;/a&gt;, les Hash aussi deviennent procable.
Rien de très excitant, cela nous permettra de récupérer la valeur depuis la clé.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#=&amp;gt; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;L'avantage c'est qu'on pourra également l'invoquer depuis un itérateur :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:z&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#=&amp;gt; [1, 2, nil]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>Précédence des opérateurs en ruby et contrôle de flux</title>
    <id>https://martin.catty.fr/blog/precedence-des-operateurs-en-ruby-et-controle-de-flux/</id>
    <link href="https://martin.catty.fr/blog/precedence-des-operateurs-en-ruby-et-controle-de-flux/" rel="alternate"/>
    <updated>2015-11-03T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Qu'est ce que la précédence ?&lt;/h3&gt;
&lt;p&gt;La précédence désigne la priorité et la force des différents opérateurs.&lt;/p&gt;
&lt;p&gt;En mathématique les différents opérateurs ont des priorités différentes, par exemple la multiplication prend le pas sur l'addition.&lt;/p&gt;
&lt;p&gt;De fait en ruby, comme dans bien d'autres langages, cette expression retourne le résultat attendu :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour reprendre la main sur le flux des opérateurs on peut bien sûr utiliser des parenthèses :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Quels sont les opérateurs ?&lt;/h3&gt;
&lt;p&gt;Les opérateurs sont les suivants, de la précédence la plus élevée à la plus faible :&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
  &lt;tr&gt;
    &lt;td&gt;[] et []=&lt;/td&gt;
    &lt;td&gt;Accès à un élément du tableau et set d'une valeur&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;**&lt;/td&gt;
    &lt;td&gt;Exponentiel&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;! ~ + -&lt;/td&gt;
    &lt;td&gt;Inversion, complément (remplace les bits 0 par 1 et vice-versa), plus et moins (-2 ou +3)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;* / %&lt;/td&gt;
    &lt;td&gt;Multiplication, division et modulo&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;+ -&lt;/td&gt;
    &lt;td&gt;Addition et soustraction&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&amp;lt;&amp;lt; &amp;gt;&amp;gt;&lt;/td&gt;
    &lt;td&gt;Décallage de bit vers la droite ou gauche. Retire des bits à droite, ajoute des 0 à gauche. Ex: (9 &gt;&gt; 1).to_s(2)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&amp;&lt;/td&gt;
    &lt;td&gt;AND, opérateur bit à bit, 1 si 1 des deux côtés, 0 sinon. (9 &amp; 14).to_s(2) donne en binaire 1001 &amp; 1110 = 1000 (8)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;^ |&lt;/td&gt;
    &lt;td&gt;^ est l'opérateur bit à bit XOR (ou exclusif). 1 si uniquement un des deux bits est à 1. | est l'opérateur bit à bit OR. 1 si 1 d'un côté au moins.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&amp;lt;= &amp;lt; &amp;gt; &amp;gt;=&lt;/td&gt;
    &lt;td&gt;Opérateurs de comparaison&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&amp;lt;=&amp;gt; == === != =~ !~&lt;/td&gt;
    &lt;td&gt;Comparaison d'égalité ou de matching&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&amp;&amp;&lt;/td&gt;
    &lt;td&gt;Et logique. Vrai si les des deux éléments sont évalués à vrai. `true &amp;&amp; false` renvoie false.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;||&lt;/td&gt;
    &lt;td&gt;Ou logique. Vrai si l'un des deux éléments est évalué à vrai. `true || false` renvoie true.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;.. ...&lt;/td&gt;
    &lt;td&gt;Range incluant et excluant la borne haute : Ex `(1..3).to_a == [1, 2, 3]`. `(1...3).to_a == [1, 2]`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;? :&lt;/td&gt;
    &lt;td&gt;Ternaire. Ex: `i % 2 == 0 ? "even" : "odd"`&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;= += -= &amp;&amp;= etc…&lt;/td&gt;
    &lt;td&gt;Affectations&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;defined?&lt;/td&gt;
    &lt;td&gt;Vérifie la définition ou non d'une variable&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;not&lt;/td&gt;
    &lt;td&gt;Négation logique&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;or and&lt;/td&gt;
    &lt;td&gt;Composition logique&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;if unless while until&lt;/td&gt;
    &lt;td&gt;Conditions et boucles&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;begin end&lt;/td&gt;
    &lt;td&gt;Blocs&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;h3&gt;Contrôle de flux versus opérateurs logiques&lt;/h3&gt;
&lt;p&gt;La précédence des opérateurs peut avoir des conséquences importantes pour des opérateurs qui semblent avoir le même usage.&lt;/p&gt;
&lt;p&gt;C'est le cas de &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; et &lt;code&gt;and&lt;/code&gt; tout comme &lt;code&gt;||&lt;/code&gt; et &lt;code&gt;or&lt;/code&gt;.
Prenons l'exemple suivant :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le dernier retour sera ici &lt;code&gt;false&lt;/code&gt; et &lt;code&gt;c&lt;/code&gt; sera &lt;code&gt;true&lt;/code&gt; ce qui peut paraître suprenant.
On le comprend mieux en prennant en compte la précédence. L'interpréteur ruby va «lire» l'expression de la sorte :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En effet l'affectation a une précédence plus forte que le &lt;code&gt;and&lt;/code&gt; comme on l'a vu dans le tableau ci dessus.&lt;/p&gt;
&lt;p&gt;Avec le &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; logique le résultat est tout autre et plus proche de ce que l'on attend naturellement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le retour est &lt;code&gt;false&lt;/code&gt; et &lt;code&gt;c&lt;/code&gt; également car l'expression se décompose de la sorte :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Il en est de même pour les opérateurs &lt;code&gt;or&lt;/code&gt; et &lt;code&gt;||&lt;/code&gt;. Il faut en fait voir les opérateurs &lt;code&gt;or&lt;/code&gt; et &lt;code&gt;and&lt;/code&gt; comme des opérateurs de gestion de flux.&lt;/p&gt;
&lt;p&gt;Leur usage est donc plus proche de celui de conditions telles que &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt; ou &lt;code&gt;unless&lt;/code&gt; que d'un opérateur logique.&lt;/p&gt;
&lt;p&gt;Vous avez sans doute déjà vu en Perl ou en PHP des instructions telles que :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;chdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'/foo/bar'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;die&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Can't cd to bar"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le fonctionnement est le même en ruby et nous permet de gérer le flux de la sorte :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/bar'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/bar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;ce qui est comparable à :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/bar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unless&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/bar'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La connaissance des opérateurs et de leur précédence est un aspect important du langage. N'hésitez pas à partager vos retours d'expériences et pièges dans lesquels vous avez pu tomber.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Du bon usage des exceptions en Ruby</title>
    <id>https://martin.catty.fr/blog/du-bon-usage-des-exceptions-en-ruby/</id>
    <link href="https://martin.catty.fr/blog/du-bon-usage-des-exceptions-en-ruby/" rel="alternate"/>
    <updated>2015-10-27T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Les exceptions servent, comme leur nom l'indique, à gérer des erreurs au caractère rare et imprévu.&lt;/p&gt;
&lt;p&gt;Elles n'ont pas vocation à gérer des erreurs prévisibles comme une erreur de saisie d'un utilisateur par exemple.&lt;/p&gt;
&lt;p&gt;Venons en rapidement aux faits :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'4'&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous renverra:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TypeError
String can't be coerced into Fixnum
./exceptions.rb:4:in `+'
./exceptions.rb:4:in `&amp;lt;main&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a donc une erreur de la classe &lt;code&gt;TypeError&lt;/code&gt;. Si on regarde la classe parente de &lt;code&gt;TypeError&lt;/code&gt; il s'agit de &lt;code&gt;StandardError&lt;/code&gt;.
Cette classe est en fait la classe par défaut utilisée dans notre &lt;code&gt;rescue&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Notre code revient donc à :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'4'&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Il est fortement déconseillé d'attraper des erreurs en dehors des sous classes de &lt;code&gt;StandardError&lt;/code&gt; car elles sont utilisées par le système.
Par exemple on y retrouve &lt;code&gt;NoMemoryError&lt;/code&gt;, &lt;code&gt;LoadError&lt;/code&gt; ou encore &lt;code&gt;SystemExit&lt;/code&gt; pour n'en citer que quelques unes.&lt;/p&gt;
&lt;p&gt;On comprend mieux pourquoi les attraper pourrait avoir des effets de bords. Quelques exemples :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Exception&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ici on attrape l'exception de type &lt;code&gt;LoadError&lt;/code&gt;, ce qui risque de masquer le problème de chargement de notre fichier.
A noter l'utilisation de la variable globale &lt;code&gt;$!&lt;/code&gt; qui récupère automatiquement la dernière exception.&lt;/p&gt;
&lt;p&gt;Autre exemple :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Say quit to esc"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;gets&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quit"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chomp&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Exception &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Bon courage pour quitter, le rescue attrapera l'exception &lt;code&gt;SystemExit&lt;/code&gt; si vous tapez &lt;code&gt;quit&lt;/code&gt; mais également &lt;code&gt;Interrupt&lt;/code&gt; si vous faites un &lt;code&gt;^C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A part pour faire une lib de debug ou autre type de wrapper ne faites pas de &lt;code&gt;rescue Exception&lt;/code&gt;.
Si vous avez besoin d'un wrapper, pensez à relancer l'exception :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# j'attrape tout, mais je sais ce que je fais&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lorsque vous avez besoin de vous assurer qu'un traitement sera réalisé même si une exception survient vous pouvez utiliser &lt;code&gt;ensure&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'tempfile'&lt;/span&gt;

&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ugh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;ensure&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notre section &lt;code&gt;ensure&lt;/code&gt; va s'assurer que le fichier est bien fermé et supprimé. J'ai volontairement re-lancé l'exception pour montrer que même si le &lt;code&gt;rescue&lt;/code&gt; relance l'exception, le passage dans le &lt;code&gt;ensure&lt;/code&gt; est garanti.&lt;/p&gt;
&lt;p&gt;Bien entendu vous pouvez créer votre propre exception comme une classe classique:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MyOwnException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;MyOwnException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si vous souhaitez attraper plusieurs types d'exception sans distinction vous pouvez parfaitement écrire :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'tempfile'&lt;/span&gt;

&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ugh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ZeroDivisionError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;ensure&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vous pouvez également gérer des comportements différents avec plusieurs instructions &lt;code&gt;rescue&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'tempfile'&lt;/span&gt;

&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ugh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# je renvoie l'erreur uniquement si c'est une exception de type ArgumentError&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ZeroDivisionError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;
&lt;span class="k"&gt;ensure&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pour connaître la liste des exceptions disponibles je vous ai préparé quelques lignes pour parcourir les différentes classes existantes et les afficher sous forme d'arbre :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;subclasses_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;├── &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;descendants_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;superclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;subclasses_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;descendants_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;ObjectSpace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;subclasses_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Résultat :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Exception
├── MonitorMixin::ConditionVariable::Timeout
├── SystemStackError
├── NoMemoryError
├── SecurityError
├── ScriptError
    ├── NotImplementedError
    ├── LoadError
        ├── Gem::LoadError
            ├── Gem::ConflictError
    ├── SyntaxError
├── StandardError
    ├── FiberError
    ├── ThreadError
    ├── Math::DomainError
    ├── LocalJumpError
    ├── IOError
        ├── EOFError
    ├── RegexpError
    ├── ZeroDivisionError
    ├── SystemCallError
        ├── Errno::ERPCMISMATCH
        ├── Errno::EPROGUNAVAIL
        ├── Errno::EPROGMISMATCH
        ├── Errno::EPROCUNAVAIL
        ├── Errno::EPROCLIM
        ├── Errno::ENOTSUP
        ├── Errno::ENOATTR
        ├── Errno::ENEEDAUTH
        ├── Errno::EFTYPE
        ├── Errno::EBADRPC
        ├── Errno::EAUTH
        ├── Errno::EOWNERDEAD
        ├── Errno::ENOTRECOVERABLE
        ├── Errno::ECANCELED
        ├── Errno::EDQUOT
        ├── Errno::ESTALE
        ├── Errno::EINPROGRESS
            ├── IO::EINPROGRESSWaitWritable
            ├── IO::EINPROGRESSWaitReadable
        ├── Errno::EALREADY
        ├── Errno::EHOSTUNREACH
        ├── Errno::EHOSTDOWN
        ├── Errno::ECONNREFUSED
        ├── Errno::ETIMEDOUT
        ├── Errno::ETOOMANYREFS
        ├── Errno::ESHUTDOWN
        ├── Errno::ENOTCONN
        ├── Errno::EISCONN
        ├── Errno::ENOBUFS
        ├── Errno::ECONNRESET
        ├── Errno::ECONNABORTED
        ├── Errno::ENETRESET
        ├── Errno::ENETUNREACH
        ├── Errno::ENETDOWN
        ├── Errno::EADDRNOTAVAIL
        ├── Errno::EADDRINUSE
        ├── Errno::EAFNOSUPPORT
        ├── Errno::EPFNOSUPPORT
        ├── Errno::EOPNOTSUPP
        ├── Errno::ESOCKTNOSUPPORT
        ├── Errno::EPROTONOSUPPORT
        ├── Errno::ENOPROTOOPT
        ├── Errno::EPROTOTYPE
        ├── Errno::EMSGSIZE
        ├── Errno::EDESTADDRREQ
        ├── Errno::ENOTSOCK
        ├── Errno::EUSERS
        ├── Errno::EILSEQ
        ├── Errno::EOVERFLOW
        ├── Errno::EBADMSG
        ├── Errno::EMULTIHOP
        ├── Errno::EPROTO
        ├── Errno::ENOLINK
        ├── Errno::EREMOTE
        ├── Errno::ENOSR
        ├── Errno::ETIME
        ├── Errno::ENODATA
        ├── Errno::ENOSTR
        ├── Errno::EIDRM
        ├── Errno::ENOMSG
        ├── Errno::ELOOP
        ├── Errno::ENOTEMPTY
        ├── Errno::ENOSYS
        ├── Errno::ENOLCK
        ├── Errno::ENAMETOOLONG
        ├── Errno::EDEADLK
        ├── Errno::ERANGE
        ├── Errno::EDOM
        ├── Errno::EPIPE
        ├── Errno::EMLINK
        ├── Errno::EROFS
        ├── Errno::ESPIPE
        ├── Errno::ENOSPC
        ├── Errno::EFBIG
        ├── Errno::ETXTBSY
        ├── Errno::ENOTTY
        ├── Errno::EMFILE
        ├── Errno::ENFILE
        ├── Errno::EINVAL
        ├── Errno::EISDIR
        ├── Errno::ENOTDIR
        ├── Errno::ENODEV
        ├── Errno::EXDEV
        ├── Errno::EEXIST
        ├── Errno::EBUSY
        ├── Errno::ENOTBLK
        ├── Errno::EFAULT
        ├── Errno::EACCES
        ├── Errno::ENOMEM
        ├── Errno::EAGAIN
            ├── IO::EAGAINWaitWritable
            ├── IO::EAGAINWaitReadable
        ├── Errno::ECHILD
        ├── Errno::EBADF
        ├── Errno::ENOEXEC
        ├── Errno::E2BIG
        ├── Errno::ENXIO
        ├── Errno::EIO
        ├── Errno::EINTR
        ├── Errno::ESRCH
        ├── Errno::ENOENT
        ├── Errno::EPERM
        ├── Errno::NOERROR
    ├── EncodingError
        ├── Encoding::ConverterNotFoundError
        ├── Encoding::InvalidByteSequenceError
        ├── Encoding::UndefinedConversionError
        ├── Encoding::CompatibilityError
    ├── RuntimeError
        ├── Gem::Exception
            ├── Gem::VerificationError
            ├── Gem::RubyVersionMismatch
            ├── Gem::RemoteSourceException
            ├── Gem::RemoteInstallationSkipped
            ├── Gem::RemoteInstallationCancelled
            ├── Gem::RemoteError
            ├── Gem::OperationNotSupportedError
            ├── Gem::InvalidSpecificationException
            ├── Gem::InstallError
            ├── Gem::ImpossibleDependenciesError
            ├── Gem::GemNotFoundException
                ├── Gem::SpecificGemNotFoundException
            ├── Gem::FormatException
            ├── Gem::FilePermissionError
            ├── Gem::EndOfYAMLException
            ├── Gem::DocumentError
            ├── Gem::GemNotInHomeException
            ├── Gem::DependencyRemovalException
            ├── Gem::DependencyError
                ├── Gem::UnsatisfiableDependencyError
                ├── Gem::DependencyResolutionError
            ├── Gem::CommandLineError
    ├── NameError
        ├── NoMethodError
    ├── RangeError
        ├── FloatDomainError
    ├── IndexError
        ├── StopIteration
        ├── KeyError
    ├── ArgumentError
        ├── UncaughtThrowError
        ├── Gem::Requirement::BadRequirementError
    ├── TypeError
├── SignalException
    ├── Interrupt
├── fatal
├── SystemExit
    ├── Gem::SystemExitException
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vous savez tout, bonne gestion d'exceptions avec Ruby !&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>WebAssembly, vers un web compilé ?</title>
    <id>https://martin.catty.fr/blog/webassembly-vers-un-web-compile/</id>
    <link href="https://martin.catty.fr/blog/webassembly-vers-un-web-compile/" rel="alternate"/>
    <updated>2015-07-10T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Edit : Les commentaires offrent un excellent complément à l'article.&lt;/p&gt;
&lt;p&gt;Il y a maintenant deux ans on commençait à voir des vidéos d'Unreal Engine 3 tournant dans le navigateur.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/BV32Cs_CMqo" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Ce proof of concept mettait en avant asm.js, un sous ensemble de JavaScript à la fois restreint mais optimisé.&lt;/p&gt;
&lt;p&gt;Concrètement asm.js offre la possibilité de transcrire du code C/C++ en JavaScript (dans la limite de ce sous ensemble) en utilisant &lt;a href="http://kripken.github.io/emscripten-site/index.html"&gt;emscripten&lt;/a&gt;. Ce code étant compilé, sa vitesse d’exécution permet d'atteindre un niveau de performance inaccessible pour un langage parsé et interprété à la volée.&lt;/p&gt;
&lt;p&gt;Pendant que la fondation Mozilla travaillait sur asm.js, Google avancait sur son &lt;a href="https://developer.chrome.com/native-client"&gt;Native Client&lt;/a&gt;, ce qui était plutôt inquiétant en terme d'intéropérablité pour nous développeurs.&lt;/p&gt;
&lt;p&gt;Heureusement les représentants des 4 navigateurs les plus utilisés ont décidé de se mettre au tour de la table et de standardiser leurs avancées dans un nouvel outil : WebAssembly (wasm).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.w3.org/community/webassembly/"&gt;Un groupe a donc était constitué&lt;/a&gt; au sein du w3c afin de spécifier WebAssembly et des &lt;a href="https://github.com/WebAssembly"&gt;dépôts ouverts&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Que va nous apporter WebAssembly ?&lt;/h3&gt;
&lt;p&gt;Dans l’exécution d'un programme classique en JavaScript le navigateur doit récupérer le code source, le compiler vers un bytecode et l'interpréter.&lt;/p&gt;
&lt;p&gt;Même avec de la compilation just in time cela prend forcément du temps. L'idée ici est de fournir directement le bytecode au navigateur. Avec à la clé plusieurs avantages : le bytecode est plus léger que le code en tant que tel, hors compression on parle d'un facteur 3, compressé avec gzip ~30%. Cela signifie 30% de données de moins à transférer et trois fois moins à traiter.&lt;/p&gt;
&lt;p&gt;La conversion du code JavaScript vers du code wasm va donc accélérer le temps de chargement de façon très significative mais moins le temps de traitement. En effet JavaScript génère également du bytecode qui est éxécuté. asm.js permet une optimisation en ne gérant qu'un sous ensemble de JavaScript et avec une syntaxe qui permet au compilateur un certain nombre d'optimisations.&lt;/p&gt;
&lt;p&gt;Concrètement on a sous le capot une version de JavaScript typée statiquement (&lt;troll&gt;on a enfin trouvé une pseudo justification à l'usage du mot java&lt;/troll&gt;) et sans objet.&lt;/p&gt;
&lt;p&gt;On ne peut donc pas faire une transposition de l'ensemble du code JavaScript vers de l'asm en un claquement de doigt. Et on ne le pourra pas non plus avec WebAssembly.&lt;/p&gt;
&lt;h3&gt;Est-ce la mort de JavaScript ?&lt;/h3&gt;
&lt;p&gt;Non, WebAssembly n'est pas prévu comme un remplaçant à court terme de JavaScript, mais il va évoluer parallèlement ce qui à mon sens est un élément indispensable à son adoption.&lt;/p&gt;
&lt;p&gt;Le web n'est pas un média de rupture, les technologies avancent certes rapidement mais en conservant de la rétro-compatibilité. Suivant cette idée, des prototypes de polyfill sont en cours afin de convertir du &lt;a href="https://github.com/WebAssembly/polyfill-prototype-1"&gt;WebAssembly vers du JavaScript&lt;/a&gt; si ce premier n'est pas supporté (assurant la compatibilité mais faisant disparaître les avantages de wasm).&lt;/p&gt;
&lt;p&gt;Toutefois le fait que les principaux navigateurs veuillent le standardiser est une excellente nouvelle et un gage d'avancées significatives à venir.&lt;/p&gt;
&lt;h3&gt;D'asm à wasm&lt;/h3&gt;
&lt;p&gt;À ce stade vous vous dites sûrement que wasm est uniquement une standardisation d'asm, mais wasm va plus loin. En effet la volonté est de pouvoir exécuter du code C/C++ dans le navigateur mais pas uniquement. À terme il sera possible d'y porter tout type de langage tout en accédant aux API de JavaScript.&lt;/p&gt;
&lt;p&gt;Même si l'effort à fournir avant d'y arriver sera énorme le potentiel est extraordinaire.&lt;/p&gt;
&lt;p&gt;Aujourd'hui des outils permettant de porter du code wasm vers asm existent (quand je lis que ce n'est pas un média de rupture), ce qui permet donc de l’exécuter dans les navigateurs supportant asm. Concrètement on parle de tous les navigateurs supportant ES6 puisque asm en est un sous ensemble.&lt;/p&gt;
&lt;h3&gt;Adieu view source ?&lt;/h3&gt;
&lt;p&gt;C'est pour moi un argument critique, cette mise à disposition du code dans un format binaire pourrait nous faire dire adieu à la possibilité d'en voir le source.&lt;/p&gt;
&lt;p&gt;Certes les codes étant maintenant compressés, minifiés voir obscurcis on peut de moins en moins facilement lire le code d'une application web depuis son navigateur. Pourtant cela reste un des fondements du web.&lt;/p&gt;
&lt;p&gt;Espérons que WebAssembly disposera d'une option de type source maps permettant de faire le lien entre le code généré et la source.&lt;/p&gt;
&lt;p&gt;En tout cas WebAssembly me semble définitivement une technologie qui va compter dans les prochaines années.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Et si on changeait la relation client - prestataire</title>
    <id>https://martin.catty.fr/blog/et-si-on-changeait-la-relation-client-prestataire/</id>
    <link href="https://martin.catty.fr/blog/et-si-on-changeait-la-relation-client-prestataire/" rel="alternate"/>
    <updated>2015-05-06T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Ah le sacro saint cahier des charges, la réponse d'appel d'offre, la short-list, les sueurs froides puis le démarrage du projet.&lt;/p&gt;
&lt;p&gt;Quelle agence n'a pas connu ça ?&lt;/p&gt;
&lt;p&gt;Ce mode de fonctionnement m'a toujours semblé étonnant. Si on repart du besoin, que cherche un client ? Généralement un partenaire fiable et compétent qui pourra l'accompagner dans la réalisation de son projet. Souvent sur du long terme.&lt;/p&gt;
&lt;p&gt;Partant de là on peut se demander si le processus ci dessus permettant de choisir le prestataire est le bon. En tant qu'agence voilà ce qui me pose problème :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Comment le client détermine à qui il envoie le cahier des charges ?&lt;/li&gt;
&lt;li&gt;Comment le client peut choisir un prestataire, à la simple lecture d'un document et de quelques échanges téléphoniques ?&lt;/li&gt;
&lt;li&gt;Y a t-il une chance qu'un mode de fonctionnement basé sur un ou des forfaits puisse pérenniser une relation ?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En tant que client :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Comment je peux cibler les prestataires compétents ? Certains sont très compétents mais peu visibles, plus souvent c'est l'inverse.&lt;/li&gt;
&lt;li&gt;Comment juger de la pertinence d'un prix ? Pas besoin de sortir de math sup pour se rendre compte qu'une mission réalisée en 3J à 750€/jour est moins chère qu'une en 10J à 250€/jour. Quel est le bon prix de la compétence de mon prestataire ?&lt;/li&gt;
&lt;li&gt;Ai je intérêt à me positionner sur un prix bas ?&lt;/li&gt;
&lt;li&gt;Comment m'assurer que mon prestataire va pouvoir m'accompagner dans la durée ?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;La diffusion&lt;/h3&gt;
&lt;p&gt;La diffusion du cahier des charges est une partie assez folklorique. Pour le client qui a fait un choix de technologie ce sera généralement son premier filtre.&lt;/p&gt;
&lt;p&gt;Cela n'a pas forcément de sens car si l'implication d'un client dans les technologies qu'il utilise me semble un très bon point il n'en sera généralement pas un bon juge (bien sûr cela exclut le client qui a sa propre équipe technique).&lt;/p&gt;
&lt;p&gt;Derrière les critères sont encore plus exotiques (années d'existence, qualité du site web, publicité…).&lt;/p&gt;
&lt;p&gt;Ici les solutions ne sont pas évidentes. D'un côté on a les plateformes d'appels d'offres publics qui sont justes mais complètement inutilisables notamment pour les petites entreprises.&lt;/p&gt;
&lt;p&gt;De l'autre on a les plateformes orientées projets type 123presta. Mais franchement je n'ai vraiment pas envie d'en parler tant cela va à l'opposé de notre idée de symbiose entre client et prestataire.&lt;/p&gt;
&lt;p&gt;Je pense qu'on devrait plutôt s'orienter vers des plateformes visant à évaluer les prestataires, faire des recommandations etc…&lt;/p&gt;
&lt;p&gt;Cela existe mais c'est un mode de fonctionnement encore marginalisé.&lt;/p&gt;
&lt;h3&gt;Investir dans la relation&lt;/h3&gt;
&lt;p&gt;En tant qu'humain on néglige quasi systématiquement les coûts indirects. Comme ils sont invisibles on a du mal à les évaluer. Pourtant dans le secteur informatique ils sont énormes.&lt;/p&gt;
&lt;p&gt;J'ai rarement vu des clients être en mesure d'évaluer le coût du changement d'un prestataire. Êtes vous sûr que choisir un prestataire 100€/J moins cher va vous faire gagner de l'argent ? Si oui, est ce vrai à court terme, à long terme ?&lt;/p&gt;
&lt;p&gt;De même il faut provisionner comme un coût le choix du prestataire. Il est beaucoup moins risqué de payer quelques jours de prestation pour juger de son niveau que de s'embarquer à l'aveugle.&lt;/p&gt;
&lt;p&gt;Oui vous aurez dépensé un peu de budget mais cela permet de se mettre dans une logique de long terme et de capitaliser sur la relation. Autrement c'est à peu près aussi fiable que de recruter une personne sur la couleur de ses yeux.&lt;/p&gt;
&lt;h3&gt;Sortir du forfait ?&lt;/h3&gt;
&lt;p&gt;Je le dis sans détour, le modèle de forfait est un modèle gagnant - perdant. Votre seul espoir est d'être du bon côté. En tant qu'agence soit vous avez fini votre projet plus vite que prévu (ou dans les temps) et vous êtes le gagnant, soit c'est l'inverse et vous pouvez regarder la marge s'envoler.&lt;/p&gt;
&lt;p&gt;Ce mode de fonctionnement a donc plusieurs effets pervers, d'un côté ou de l'autre :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Si j'ai été perdant le coup d'avant je vais essayer de compenser&lt;/li&gt;
&lt;li&gt;En tant qu'agence si je me rapproche du côté perdant la solution de facilité est de limiter la qualité&lt;/li&gt;
&lt;li&gt;Je vais cacher à l'autre qu'il est le perdant&lt;/li&gt;
&lt;li&gt;Je fais des estimations que je gonfle pour être sûr de ne pas être le perdant&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Qu'est ce qui se passe quand on a été perdant plusieurs fois d'affilé ? La relation se termine (généralement mal).&lt;/p&gt;
&lt;p&gt;Selon moi la première chose à faire est de démystifier ce sujet. Parlons en ouvertement entre client et agence pour trouver de bonnes solutions. Le mode de fonctionnement en régie est sans doute beaucoup plus juste s'il n'a pas pour but de prendre le client en otage. Il implique donc de mettre en place des gardes fous:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quel est le niveau de qualité souhaité ? La qualité est un indicateur  complètement abstrait pour lequel il faut se mettre d'accord avec le client.&lt;/li&gt;
&lt;li&gt;Le temps de développement d'une fonctionnalité. C'est à dire être en capacité d'alerter le client quand le développement dépasse un nombre de jours entendus.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En ce sens je ne rejoins pas la logique puriste NoEstimates. L'estimation est un indicateur permettant de faire des choix et de piloter un développement. Il permet de pondérer une difficulté et d'en déduire un cap pour le produit.&lt;/p&gt;
&lt;p&gt;Si vous êtes un puriste je serai curieux de savoir ce que vous direz au constructeur de votre maison quand vous lui demanderez quand vous pourrez habiter dedans et qu'il vous dira qu'il ne fait pas d'estimation.&lt;/p&gt;
&lt;p&gt;Attention, contracter des estimations n'a pas de sens non plus. D'où l'idée de pouvoir se mettre d'accord en amont entre client et prestataire.&lt;/p&gt;
&lt;p&gt;Enfin gardons en tête le sens premier du mot estimation. Si votre client n'accepte pas que vous puissiez vous tromper c'est soit qu'il ne connait pas le sens du mot soit qu'il ne souhaite pas être dans une relation où les risques sont mitigés.&lt;/p&gt;
&lt;p&gt;L'autre avantage d'estimer est de pouvoir regrouper des fonctionnalités pour en faire des séquences de développements (sprints). Partant de là on se donne des priorités mais on est également en mesure de rapidement voir d'où vient le problème lorsqu'on déborde d'une estimation.&lt;/p&gt;
&lt;p&gt;Il n'y a pas de honte à dire que l'estimation n'était pas juste. C'est une estimation. D'ailleurs bien souvent le client l'entendra, à la condition que vous l'ayez prévenu dès l'origine plutôt qu'en le mettant au pied du mur.&lt;/p&gt;
&lt;p&gt;Encore une fois l'idée est d'être juste des deux côtés. Si vous attendez cette compréhension de votre client cela signifie que côté prestataire vous vous imposez également une transparence pour prévenir le client que vous avez été plus vite que prévu.&lt;/p&gt;
&lt;h3&gt;Rapprocher les frontières&lt;/h3&gt;
&lt;p&gt;Toute la subtilité des deux côtés (client et prestataire) est de s'impliquer et comprendre le métier de l'autre au mieux sans interférer. Je comprends ton domaine de compétence pour rapprocher le mien du tien ; mais je ne te dis pas ce que tu dois faire dans ton domaine.&lt;/p&gt;
&lt;p&gt;Il faut chercher à augmenter l'intersection entre les deux domaines de compétences pour en sortir un projet réussi car il n'y a pas de bon projet sans implication.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/reussite_echec.png"&gt;&lt;img alt="Projet réussi vs projet raté" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/reussite_echec_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;La relation client fournisseur est un sujet complexe que j'ai effleuré et je ne prétends pas avoir de réponse à chacune des problématiques.&lt;/p&gt;
&lt;p&gt;Quel est votre retour d'expérience ? Avez vous des idées pour améliorer cette relation ?&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Servir rapidement ses images avec nginx et dragonfly</title>
    <id>https://martin.catty.fr/blog/servir-rapidement-ses-images-avec-nginx-et-dragonfly/</id>
    <link href="https://martin.catty.fr/blog/servir-rapidement-ses-images-avec-nginx-et-dragonfly/" rel="alternate"/>
    <updated>2015-02-27T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Lorsqu'on utilise la gem dragonfly, le chargement des images de notre application ressemble à cela :&lt;/p&gt;
&lt;pre&gt;
I, [2015-02-27T16:16:32.957778 #3191]  INFO -- : DRAGONFLY: GET /media/W1siZiIsIjIwMTUvMDIvMjEvOWoxOXhscmMzMV9NX2VuZmxhbW1lX2xlc19jZXNhcl8yMDE1XzAuanBnIl0sWyJwIiwidGh1bWIiLCI3OTJ4NDAwIyJdXQ/-M--Ceremonie%20des%20Cesar%2C%20M%20fait%20son%20cinema-actu_0?sha=ca1afcbcf31555c8 200
&lt;/pre&gt;

&lt;p&gt;On ne peut donc pas demander à nginx de servir directement le fichier depuis le système de fichier car son URL ne correspond pas à son chemin sur le serveur.&lt;/p&gt;
&lt;p&gt;C'est problématique car on va demander à notre serveur ruby de servir ces images à chaque fois, ce qui va s'avérer peu performant.&lt;/p&gt;
&lt;h3&gt;Mise en place d'un proxy cache&lt;/h3&gt;
&lt;p&gt;Nginx nous offre une directive &lt;a href="http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path"&gt;&lt;code&gt;proxy_cache_path&lt;/code&gt;&lt;/a&gt; pour faire en sorte de mettre en cache le résultat de certaines URL.&lt;/p&gt;
&lt;p&gt;Tout d'abord nous allons créer notre répertoire de cache :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/etc/nginx/cache/dragonfly
chown&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;nobody:root&lt;span class="w"&gt; &lt;/span&gt;/etc/nginx/cache/dragonfly
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;puis référencer notre cache dans notre fichier nginx.conf.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http {
  proxy_cache_path /etc/nginx/cache/dragonfly levels=2:2 keys_zone=dragonfly:100m inactive=30d max_size=1g;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La directive &lt;code&gt;levels&lt;/code&gt; définit la profondeur des dossiers créés, ex: &lt;code&gt;/etc/nginx/cache/dragonfly/aa/11&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;En 1:1 on aurait quelquechose comme &lt;code&gt;/etc/nginx/cache/dragonfly/a/1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cette arboresence est systématiquement reprise dans les fichiers stockés dans ces dossiers de sorte qu'ils puissent retrouver simplement leur place en cas de déplacement, ex: &lt;code&gt;/etc/nginx/cache/dragonfly/ff/84/718ba36b52491e18c227650da4e884ff&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;La directive &lt;code&gt;keys_zone&lt;/code&gt; définit le nom de notre cache et la taille pour les clés. Il s'agit bien des clés de cache et pas des fichiers mis en cache. Une zone de 1M permet de stocker 8 000 clés.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;inactive&lt;/code&gt; permet de retirer du cache un fichier non accédé depuis un certain temps. Par défaut c'est 10 minutes, ici on utilise 30 jours.&lt;/p&gt;
&lt;p&gt;Enfin &lt;code&gt;max_size&lt;/code&gt; définit la taille cumulée des fichiers mis en cache.
Quand cette taille est atteinte nginx retire les entrées qui ont été utilisées il y a le plus longtemps.&lt;/p&gt;
&lt;p&gt;Si un fichier a été mis en cache il y 20 jours et accédé aujourd'hui il sera prioritaire par rapport à un fichier mis en cache il y a 2 jours et accédé hier.&lt;/p&gt;
&lt;h3&gt;Cache partagé entre plusieurs sites&lt;/h3&gt;
&lt;p&gt;Si notre nginx fait tourner plusieurs sites il est préférable de déterminer également une clé de cache qui nous évitera les conflits.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http {
  proxy_cache_key &amp;quot;$scheme://$host$request_uri&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Configuration du vhost&lt;/h3&gt;
&lt;p&gt;Il nous reste maintenant uniquement à configurer le site en question.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  location /media {
    proxy_pass http://app;
    proxy_cache dragonfly;
    proxy_cache_valid 200 30d;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nous demandons donc à servir les fichiers ayant une URL débutant par /media par notre proxy_pass s'il ne sont pas en cache et de les y mettre.&lt;/p&gt;
&lt;p&gt;La directive &lt;code&gt;proxy_cache_valid&lt;/code&gt; nous permet de spécifier une durée de validité du cache par status de réponse, par exemple 30 jours pour une 200, 1 minute pour une 404 etc…&lt;/p&gt;
&lt;p&gt;C'est tout pour cet article, j'espère qu'il vous aidera à rendre le web un peu plus rapide.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Comprendre et implémenter un DSL en ruby</title>
    <id>https://martin.catty.fr/blog/comprendre-et-implementer-un-dsl-en-ruby/</id>
    <link href="https://martin.catty.fr/blog/comprendre-et-implementer-un-dsl-en-ruby/" rel="alternate"/>
    <updated>2014-12-23T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Lorsqu'on débute en Ruby, les &lt;acronym title="Domain Specific Languages"&gt;DSL&lt;/acronym&gt; apparaissent comme une sorte de magie noire alors qu'on les croise à chaque coin de son éditeur.&lt;/p&gt;
&lt;p&gt;Il suffit d'ouvrir un fichier d'environnement dans une application Ruby on Rails pour tomber dessus:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Settings specified here will take precedence over those in config/application.rb.&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# In the development environment your application's code is reloaded on&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# every request. This slows down response time but is perfect for development&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# since you don't have to restart the web server when you make code changes.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache_classes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Do not eager load code on boot.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eager_load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vous le voyez, dans les DSL tout repose sur l'usage de blocs.
Dans cet article je vous présenter un exemple basique dans l'exposition d'un DSL sans intérêt et un exemple plus avancé permettant d'utiliser un DSL pour définir un DOM de la sorte:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"post"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my_firstname"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"firstname"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Qu'est ce qu'un DSL ?&lt;/h3&gt;
&lt;p&gt;Un DSL est l'application d'un langage particulier dans un domaine précis. Comparativement au langage parlé, c'est l'équivalent d'un jargon.&lt;/p&gt;
&lt;p&gt;C'est à dire que vous allez utiliser un sous ensemble d'un langage pour exprimer un contexte précis.&lt;/p&gt;
&lt;h3&gt;Créer son DSL&lt;/h3&gt;
&lt;p&gt;Prenons un cas simple où je souhaite pouvoir décrire des services à l'aide d'une syntaxe encadrée.&lt;/p&gt;
&lt;p&gt;Je voudrai pouvoir exprimer mes services de la sorte:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"foo.bar.com"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"db"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"jean"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"fam0us"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On le voit, la configuration d'un service avec cette syntaxe est très lisible et facilement mémorisable. On est finalement assez proche de l'écriture d'un fichier de configuration.&lt;/p&gt;
&lt;p&gt;Voyons ce qu'il nous faut pour rendre notre interface de service disponible.&lt;/p&gt;
&lt;p&gt;On se doute qu'il va nous falloir une classe avec un constructeur qui prend un bloc. On va invoquer ce bloc s'il est présent (toute méthode ruby possède un bloc optionnel). Pour cela nous utiliserons yield.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;block_given?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# en fait l'appel description("postgresql")&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Aïe: &lt;code&gt;undefined method 'description' for main:Object&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Apparemment notre bloc essaye d'invoquer &lt;code&gt;description&lt;/code&gt; au niveau main, vérifions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;block_given?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not sure you want to call me."&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ruby&lt;span class="w"&gt; &lt;/span&gt;service.rb
Not&lt;span class="w"&gt; &lt;/span&gt;sure&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;want&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;me.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Effectivement notre bloc ne s’exécute pas dans le contexte du service, il prend le contexte courant. À ce moment &lt;code&gt;self&lt;/code&gt; ne représente donc pas l'instance de &lt;code&gt;Service&lt;/code&gt; mais le main.&lt;/p&gt;
&lt;p&gt;Nous pourrions améliorer notre programme de la sorte pour passer notre contexte au yield:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;block_given?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Bon, ça marche, mais on ne respecte pas la syntaxe initiale souhaitée.
Pour ce faire nous avons besoin d'évaluer notre bloc dans le contexte de notre objet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;block_given?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En faisant cela on applique un &lt;code&gt;self.instance_eval(&amp;amp;block)&lt;/code&gt;. &lt;code&gt;self&lt;/code&gt; étant à cet endroit l'instance de &lt;code&gt;Service&lt;/code&gt; cela fonctionne normalement.&lt;/p&gt;
&lt;p&gt;Si notre DSL doit gérer beaucoup d'attributs on pourra l'enrichir pour gérer dynamiquement nos setters, ou utiliser &lt;code&gt;method_missing&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On pourrait même s'amuser à créer une petite interface pour rendre n'importe quelle classe utilisable comme un DSL.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;DSL&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;DSL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Gare au contexte&lt;/h3&gt;
&lt;p&gt;Nous voilà donc arrivé au résultat voulu en finalement très peu de code. Mais je sens que quelque chose vous chagrine.&lt;/p&gt;
&lt;p&gt;Vous vous dites que c'est super mais dans ce cas pourquoi des DSL type create_table dans &lt;code&gt;ActiveRecord::Migration&lt;/code&gt; sont sous cette forme:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:contacts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamps&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;et pas celle ci:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:contacts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;timestamps&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vous le savez sans doute, l'évaluation de code est à utiliser modérément. Dans le contexte de mon bloc je vais être cantonné à mon binding, c'est à dire celui de mon service.&lt;/p&gt;
&lt;p&gt;Si je souhaite mettre en place un peu d'abstraction cela commence à poser souci:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;block_given?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ServiceDefinition&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;SERVICES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'postgresql'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:pg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;SERVICES&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;undefined method name for #&amp;lt;Service:0x007f93ba10c930&amp;gt;&lt;/code&gt;. Je ne peux pas appeler &lt;code&gt;name&lt;/code&gt; ici car il le cherche sur mon service.&lt;/p&gt;
&lt;p&gt;Si je veux arriver au bon résultat je vais devoir sauvegarder mon contexte avant le bloc:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;block_given?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ServiceDefinition&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;SERVICES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'postgresql'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;Service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:pg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;SERVICES&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dans le cas présent on voit que ce n'est pas une excellente idée ; attention donc à être sûr de ce qu'on fait et ne pas se créer des problèmes vicieux et complexe à débugger par la suite.&lt;/p&gt;
&lt;h3&gt;Simuler un DOM avec un DSL&lt;/h3&gt;
&lt;p&gt;Voyons maintenant comment mettre en place un DSL un tant soit peu utile permettant de définir un document structuré type page HTML.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/synbioz/dsl_example/blob/master/dom.rb"&gt;Le fichier dom.rb est sur notre compte github&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;WithAttributes&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# use attr_ prefix to be able to filter these var later.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;instance_variable_set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@attr_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Je définis un module qui me permettra de définir des attributs dans mes différentes classes représentant mes éléments.&lt;/p&gt;
&lt;p&gt;Ces attributs sont placés dans des variables d'instances. Par exemple pour un attribut &lt;code&gt;name&lt;/code&gt; c'est &lt;code&gt;@attr_name&lt;/code&gt; qui sera créée ainsi que sont setter associé &lt;code&gt;def name(name)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;L'intérêt d'utiliser un nom préfixé est de pouvoir lister et filtrer ces attributs par la suite.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Element&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;extend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;WithAttributes&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@childs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;block_given?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;to_html&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downcase&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@childs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_html&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# only keep attr variables&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;instance_variables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^@attr_/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slice!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@attr_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;instance_variable_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Element est ma classe de base, celle dont hériteront les autres classes. J'utilise le désormais connu &lt;code&gt;instance_eval(&amp;amp;block)&lt;/code&gt; qui permettra de donner des valeurs à mes attributs en utilisant les méthodes définies dynamiquement.&lt;/p&gt;
&lt;p&gt;J'ai une méthode &lt;code&gt;to_html&lt;/code&gt; récursive qui affiche ma balise avec ses attributs et s'invoque sur les éléments enfants.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Element&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@childs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;P&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Element&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@childs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Element&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le reste est très simple, ce sont des classes qui héritent de ma classe &lt;code&gt;Element&lt;/code&gt; et exposent leurs méthodes et attributs.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Le concept de DSL est à la fois puissant tout en étant simple à implémenter. Si vous souhaitez aller plus loin sans repartir de 0 à chaque fois vous pouvez également jeter un œil à la &lt;a href="https://github.com/ms-ati/docile"&gt;gem docile&lt;/a&gt; qui vous permet de mettre en place des DSL très simplement.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Manage your metas with metaslug gem</title>
    <id>https://martin.catty.fr/en/blog/manage-your-metas-with-metaslug-gem/</id>
    <link href="https://martin.catty.fr/en/blog/manage-your-metas-with-metaslug-gem/" rel="alternate"/>
    <updated>2014-10-14T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;&lt;a href="http://rubygems.org/gems/metaslug"&gt;Metaslug&lt;/a&gt; is a gem designed to let you easily manage your metas, based on a locale and an URL (a path to be more accurate).&lt;/p&gt;
&lt;h3&gt;Why metaslug ?&lt;/h3&gt;
&lt;p&gt;For quite some time we've been looking for a gem to let us manage metas of our applications outside the codebase with these simple requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Metas are based on a path (slug)&lt;/li&gt;
&lt;li&gt;Metas can be different according to the locale&lt;/li&gt;
&lt;li&gt;Being a developer should not be a requirement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By metas we mean: title, description, opengraph tag…&lt;/p&gt;
&lt;p&gt;We used &lt;a href="https://github.com/lassebunk/metamagic"&gt;metamagic&lt;/a&gt; which does the job but we still need to manage our metas inside the differents views and have to deal with the locale.&lt;/p&gt;
&lt;h3&gt;How it works&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/synbioz/metaslug"&gt;Our README&lt;/a&gt; is a good starting point.&lt;/p&gt;
&lt;p&gt;After installing the gem with bundler (&lt;code&gt;gem metaslug&lt;/code&gt; in your gemfile) you will generate your locales files based on your routes and use our helper &lt;code&gt;metaslug&lt;/code&gt; in your layout.&lt;/p&gt;
&lt;p&gt;Metaslug uses simple YAML file to store metas in &lt;code&gt;config/metaslug/{en,fr…}.yml&lt;/code&gt;.
Your locale file will contain a defaut section, allowing you to set default
metas for every page without explicit ones.&lt;/p&gt;
&lt;p&gt;The syntax is exactly the same as in the routes, with parameters like &lt;code&gt;:id&lt;/code&gt; or so.&lt;/p&gt;
&lt;h3&gt;Dynamic metas&lt;/h3&gt;
&lt;p&gt;Of course your YAML file can have dynamic content. We use &lt;a href="https://github.com/Shopify/liquid"&gt;liquid&lt;/a&gt; to let you set your content dynamically.&lt;/p&gt;
&lt;p&gt;In the YAML it will result in something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"/posts/:id/edit"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Edit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{post.title}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In your model you'll have to define the methods accessible by liquid:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;liquid_methods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And in your controllers you will define which ivar is accessible with &lt;code&gt;metaslug_vars :post, only: :edit&lt;/code&gt;. As you can see &lt;code&gt;metaslug_vars&lt;/code&gt; has the same syntax as a before_filter. In fact it is a before_filter.&lt;/p&gt;
&lt;p&gt;It just marks these variables as accessible and will interpolate them after the action and before the rendering.&lt;/p&gt;
&lt;p&gt;Please notice metas are reloaded for each request in development mode only.&lt;/p&gt;
&lt;h3&gt;Deep metas&lt;/h3&gt;
&lt;p&gt;There is no particular limitation for metas. This kind of metas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"og:locale:alternate"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fr_FR"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Will result in:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s"&gt;"/awesome-page"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;og&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;alternate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"fr_FR"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There is no control, you can even define your own meta if you want.&lt;/p&gt;
&lt;h3&gt;What if everything is dynamic?&lt;/h3&gt;
&lt;p&gt;So you're app as a lot of dynamic content, or maybe you use a CMS ? Great! Metaslug let you write something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# catch all slug&lt;/span&gt;
&lt;span class="s"&gt;"/:slug"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{page.title}}"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{page.description}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Just remind that the keys are browsed sequentially, you have to put this generic expression at the bottom of your file.&lt;/p&gt;
&lt;h3&gt;Technical points and roadmap&lt;/h3&gt;
&lt;p&gt;We actually just compare the &lt;code&gt;request.path&lt;/code&gt; to our YAML keys, converted to regexp. It may sound a good idea to reuse the rails router but our goal is to avoid deep coupling with rails.&lt;/p&gt;
&lt;p&gt;For the next major version we want to be fully compatible with any rack-based framework.&lt;/p&gt;
&lt;p&gt;You can view the YAML files as a current backend solution but it won't be the only one. It's still way to complicate for a customer to edit this kind of files and commit them to the repository. Plus it will require to reload the application.&lt;/p&gt;
&lt;p&gt;Insteal, we will add a user interface to allow metas edition from the application.
To do this we will need to introduce different storage like database, cache…&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;We really hope &lt;a href="http://rubygems.org/gems/metaslug"&gt;Metaslug&lt;/a&gt; will help you manage your metas more easily.&lt;/p&gt;
&lt;p&gt;Feel free to suggest some feature or even code it and make a pull request.&lt;/p&gt;
&lt;p&gt;The Synbioz Team&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Demander à nginx de ne répondre que pour un nom de domaine exact</title>
    <id>https://martin.catty.fr/blog/demander-a-nginx-de-ne-repondre-que-pour-un-nom-de-domaine-exact/</id>
    <link href="https://martin.catty.fr/blog/demander-a-nginx-de-ne-repondre-que-pour-un-nom-de-domaine-exact/" rel="alternate"/>
    <updated>2014-08-21T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Nginx répond dès que possible&lt;/h3&gt;
&lt;p&gt;Mettons que vous ayez un wilcard DNS qui pointe vers votre serveur.&lt;/p&gt;
&lt;p&gt;Par exemple *.domain.tld pointe sur une IP donnée.&lt;/p&gt;
&lt;p&gt;Si vous avez une application a.domain.tld et b.domain.tld configurée sur votre
serveur, que se passe t-il si vous demandez l'URL c.domain.tld ?&lt;/p&gt;
&lt;p&gt;Et bien Nginx va tenter de répondre en affichant l'un des deux sites configurés.
En effet si vous avez une directive de type :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;a.domain.tld&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cela ne signifie pas que nginx redirigera sur votre application uniquement lorsque vous
demandez a.domain.tld.&lt;/p&gt;
&lt;h3&gt;Rendre nginx plus strict sur les noms de domaine&lt;/h3&gt;
&lt;p&gt;Pour obtenir un matching strict du nom de serveur vous devez d'abord renvoyer
une 404 dans une directive server globale.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;a.domain.tld&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En effet nginx fonctionne sur le principe du plus générique au plus spécifique.&lt;/p&gt;
&lt;p&gt;Vous lui indiquez donc que dans le cas général vous voulez retourner une 404,
et dans le particulier du bon nom de domaine vous voulez effectuer le traitement
indiqué.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>CORS, CloudFront et Firefox sont dans un bateau</title>
    <id>https://martin.catty.fr/blog/cors-cloudfront-et-firefox-sont-dans-un-bateau/</id>
    <link href="https://martin.catty.fr/blog/cors-cloudfront-et-firefox-sont-dans-un-bateau/" rel="alternate"/>
    <updated>2014-07-03T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Le mécanisme CORS (Cross-origin resource sharing) permet de charger des ressources,
notamment des assets, depuis un autre domaine que le domaine utilisé par une application web.&lt;/p&gt;
&lt;p&gt;Par exemple mon application est sur app.com, je veux charger une image depuis assets.com.&lt;/p&gt;
&lt;p&gt;L'intérêt est d'ouvrir son application à l'utilisation de &lt;a href="http://fr.wikipedia.org/wiki/Content_delivery_network"&gt;CDN&lt;/a&gt; et de servir ses fichiers statiques depuis plusieurs endroits et au plus prêt de l'utilisateur final.&lt;/p&gt;
&lt;p&gt;En effet si je dois charger une image depuis mon application, un CDN comme CloudFront me la renverra
probablement depuis un endroit différent selon que j'utilise l'application depuis l'Europe ou les
états Unis.&lt;/p&gt;
&lt;p&gt;D'autre part, chaque navigateur peut ouvrir un nombre limité de connexions HTTP vers un même domaine.
Les dernières versions des navigateurs sont généralement autour de 6/8.&lt;/p&gt;
&lt;p&gt;Cela signifie que si vous chargez 10 fichiers depuis un même domaine et que votre navigateur peut
ouvrir 8 connexions, il devra attendre d'avoir chargé le 1er fichier pour charger le 9ème.&lt;/p&gt;
&lt;p&gt;Ce «problème» peut facilement être contourné en faisant pointer des sous domaines de son application
vers son CDN. C'est généralement ce qu'on fait avec son sous domaine CloudFront.&lt;/p&gt;
&lt;p&gt;C'est à dire faire pointer cdn{1..4}.app.com vers assets.com au niveau DNS.&lt;/p&gt;
&lt;h3&gt;Est ce que je peux utiliser CORS dès maintenant ?&lt;/h3&gt;
&lt;p&gt;Le support de CORS par les navigateurs est plus que bon puisque toutes les dernières versions
des &lt;a href="http://caniuse.com/#feat=cors"&gt;principaux navigateurs le supportent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Par contre les mécanismes de sécurité mis en place d'un navigateur à l'autre ne sont pas les même.&lt;/p&gt;
&lt;p&gt;En effet, la possibilité d'ouvrir une ressource depuis un autre domaine peut poser quelque soucis
de sécurité, puisque par définition on charge un contenu sur lequel on n'a pas de contrôle.&lt;/p&gt;
&lt;p&gt;Par exemple les requêtes Ajax était traditionnellement limitées à une police de sécurité stricte des navigateurs,
les cantonnant au même domaine. Ce n'est plus le cas depuis l'avènement d'HTML5.&lt;/p&gt;
&lt;p&gt;Pour éviter que tout le monde puisse charger n'importe quel contenu, le mécanisme CORS a été mis en place.&lt;/p&gt;
&lt;p&gt;Il prévoit que la source du contenu chargé doit explicitement spécifier les domaines qu'elle accepte pour accéder à ses ressources, via l'entête &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;En tant que domaine assets.com, je peux donc annoncer que seul de domaine app.com peut accéder aux ressources proposées.&lt;/p&gt;
&lt;p&gt;Ces mécanismes de protection s'appliquent aux JavaScript mais également aux webfont dans le cas de Firefox.
Autant Safari et Chrome ne broncheront pas si vous essayez de charger une font type .woff depuis un CDN,
autant Firefox s'attend à recevoir les entêtes précisant que le domaine de destination nous y autorise.&lt;/p&gt;
&lt;p&gt;Cela signifie que lorsqu'on utilise un CDN comme CloudFront on doit renvoyer ces entêtes sinon la
font en question ne sera pas chargée.&lt;/p&gt;
&lt;p&gt;Ce n'est peut être pas très grave dans le cas d'une font de caractères ou vous avez sûrement un fallback vers
une font classique ; mais dans le cas d'une font d'images c'est plus gênant, voyez plutôt:&lt;/p&gt;
&lt;p&gt;&lt;img alt="font" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/font.png" /&gt;&lt;/p&gt;
&lt;h3&gt;Mettre en place CORS avec CloudFront&lt;/h3&gt;
&lt;p&gt;Le fonctionnement de CloudFront est simple. Imaginons que vous l'utilisiez pour vos images.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;amp;lt;img src=&amp;quot;//app.CloudFront.net/images/awesome.png&amp;quot; alt=&amp;quot;Awesome image&amp;quot; /&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si l'image est présente sur votre CDN il la sert directement, si ce n'est pas le cas il la demande
à votre application et la met en cache pour la servir directement la fois prochaine.&lt;/p&gt;
&lt;p&gt;C'est donc bien au niveau du serveur web de votre application qu'il va falloir renvoyer les entêtes
adaptés.&lt;/p&gt;
&lt;p&gt;En l'occurrence dans Nginx:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location ~* \.(eot|ttf|woff)$ {
  add_header &amp;quot;Access-Control-Allow-Origin&amp;quot; &amp;quot;app.com&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pour que CloudFront ait «connaissance» de votre application il faut lui donner l'URL de celle
via l'onglet Origin.&lt;/p&gt;
&lt;p&gt;Lorsque vous faites une requête sur CloudFront, celui ci renvoie les même entêtes que si c'était
votre application qui répondait.&lt;/p&gt;
&lt;p&gt;Dès lors qu'on consultera un .woff on enverra donc l'entête &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Côté CloudFront il faut s'assurer qu'il laisse passer l'origine. Rendez vous sur votre console AWS, CloudFront,
choisissez Distribution Settings, Onglet Behaviors.&lt;/p&gt;
&lt;p&gt;Nous allons maintenant indiquer que nous souhaitons transférer certains entête de la requête initiale,
notamment l'origine.&lt;/p&gt;
&lt;p&gt;En effet pour simplifier la mise en cache CloudFront ne retient par défaut aucun entête.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/headers.png"&gt;&lt;img alt="Ajout d'entête" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/headers_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;C'est tout pour la mise en place de CORS avec CloudFront&lt;/p&gt;
&lt;h3&gt;Un coup d'œil sur un autre CDN, google fonts&lt;/h3&gt;
&lt;p&gt;Regardons les headers retournés par Google fonts lorsqu'on charge une font:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget --server-response -O /dev/null http://themes.googleusercontent.com/static/fonts/opensans/v8/cJZKeOuBrn4kERxqtaUH3bO3LdcAZYWl9Si6vvxL-qU.woff
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;HTTP request sent, awaiting response...
  HTTP/1.1 200 OK
  Content-Type: font/woff
  Last-Modified: Tue, 28 Jan 2014 23:51:09 GMT
  Date: Wed, 02 Jul 2014 14:25:43 GMT
  Expires: Thu, 02 Jul 2015 14:25:43 GMT
  Access-Control-Allow-Origin: *
  Timing-Allow-Origin: *
  X-Content-Type-Options: nosniff
  Server: sffe
  Content-Length: 14604
  X-XSS-Protection: 1; mode=block
  Cache-Control: public, max-age=31536000
  Age: 416552
  Alternate-Protocol: 80:quic
Length: 14604 (14K) [font/woff]
Saving to: '/dev/null'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Google fonts agit ici comme un CDN, l'avantage est que la font a pu être potentiellement déjà chargée par une autre application, et par conséquent peut déjà se retrouver dans le cache du navigateur client.&lt;/p&gt;
&lt;p&gt;Ici l'entête qui nous intéresse le plus est le header &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt;,
qui indique que le contenu peut être chargé depuis n'importe quel origine. On peut donc parfaitement
utiliser un wildcard pour cet entête.&lt;/p&gt;
&lt;p&gt;On notera que la mise en cache est particulièrement longue (1 an), puisque ce contenu n'a pas vocation
à changer régulièrement.&lt;/p&gt;
&lt;h3&gt;Le mot de la fin concernant CORS&lt;/h3&gt;
&lt;p&gt;CORS ne se limite pas au verbe HTTP GET, en effet la spec prévoit qu'il est possible de faire du CORS
en POST par exemple. Dans ce cas le navigateur doit envoyer une requête avant (preflight) afin de demander
quelles méthodes il peut utiliser pour cette ressource.&lt;/p&gt;
&lt;p&gt;J'espère que cet article vous permettra d'y voir plus clair sur l'intérêt et le fonctionnement de CORS.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Lancement d'Hackademy</title>
    <id>https://martin.catty.fr/blog/lancement-dhackademy/</id>
    <link href="https://martin.catty.fr/blog/lancement-dhackademy/" rel="alternate"/>
    <updated>2014-04-10T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Hackademy&lt;/h3&gt;
&lt;p&gt;Après 4 ans de rédaction d'articles et prês de 200 billets au compteur, nous avons
fait le constat que nous aimions toujours autant partager nos retours d'expériences et
la connaissance de nos outils ; mais également que cela vous plaisait beaucoup.&lt;/p&gt;
&lt;p&gt;Nous avons donc naturellement décidé d'aller plus loin en lançant &lt;a href="https://hackademy.io"&gt;Hackademy, une plateforme
de tutoriels vidéos destinés aux métiers du web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Étant donné le peu de couverture francophone les vidéos seront disponibles en français.&lt;/p&gt;
&lt;h3&gt;Quel format ?&lt;/h3&gt;
&lt;p&gt;Nous souhaitons diffuser des vidéos courtes, de 5 à 10 minutes, qui vous permettront
soit d'avoir un tour d'horizon d'un outil ou d'une technologie, soit d'approfondir
un sujet et d'obtenir un point de vue averti.&lt;/p&gt;
&lt;p&gt;Notre constat est que tester et s'approprier des outils ou techniques à disposition est
très chronophage. Ce travail de veille et d'expertise est pourtant fondamental puisqu'il
permet de choisir et maîtriser ses outils.&lt;/p&gt;
&lt;p&gt;En mutualisant cette expertise nous voulons vous faire gagner du temps en
tant que développeur, designer, intégrateur, bidouilleur… afin que vous
puissiez être plus efficace et également trouver et contrôler les outils que vous aimerez utiliser
au quotidien.&lt;/p&gt;
&lt;h3&gt;De quoi ça parle ?&lt;/h3&gt;
&lt;p&gt;Nous avons délimité 4 thématiques assez larges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le développement (on parlera de ruby, go, python, performances…)&lt;/li&gt;
&lt;li&gt;Le développement opérationnel / devops (on parlera de vagrant, docker…)&lt;/li&gt;
&lt;li&gt;L'UI/UX (on parlera de design, d'intégration et de leurs outils…)&lt;/li&gt;
&lt;li&gt;Data (on parlera de SQL, NoSQL, traitement de données, ETL…)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nous sommes ouverts sur leur contenu, mais également sur les catégories en elles-mêmes.&lt;/p&gt;
&lt;h3&gt;Pourquoi une plateforme ?&lt;/h3&gt;
&lt;p&gt;Le mot plateforme prend son sens dans la contribution. Nous souhaitons qu'&lt;a href="https://hackademy.io"&gt;Hackademy&lt;/a&gt;
soit à disposition en consultation et en diffusion. Que chacun puisse y poster
ses vidéos.&lt;/p&gt;
&lt;p&gt;Pour cela nous avons préparé des outils à base de machine virtuelle afin de
faciliter la réalisation des vidéos.&lt;/p&gt;
&lt;p&gt;Si vous souhaitez proposer un sujet, &lt;a href="https://hackademy.io/contact"&gt;nous vous invitons à nous contacter&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Est ce payant ? Puis je être rémunéré ?&lt;/h3&gt;
&lt;p&gt;La plupart des vidéos seront gratuites, certaines seront réservées à nos abonnés payants.
Les contributions sous forme de vidéos payantes seront rémunérées à leurs auteurs.&lt;/p&gt;
&lt;p&gt;Pour les contributions gratuites elles sont un excellent moyen de se donner de la visibilité,
par exemple pour un &lt;a href="http://www.synbioz.com/blog/freelance_ou_agence_comment_choisir"&gt;freelance&lt;/a&gt;
qui souhaite mettre en avant son expertise.&lt;/p&gt;
&lt;h3&gt;Ça commence quand ?&lt;/h3&gt;
&lt;p&gt;La machine est lancée, nous avons d'ores et déjà produit 3 vidéos et la suite arrive.&lt;/p&gt;
&lt;p&gt;Que vous aimiez l'initiative ou non, que vous ayez envie de contribuer ou pas, plus que
tout nous aimerions avoir votre avis. Dites nous ce que vous en pensez !&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Freelance ou agence, qui choisir et pourquoi ?</title>
    <id>https://martin.catty.fr/blog/freelance-ou-agence-qui-choisir-et-pourquoi/</id>
    <link href="https://martin.catty.fr/blog/freelance-ou-agence-qui-choisir-et-pourquoi/" rel="alternate"/>
    <updated>2014-03-18T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;J'ai conscience que ce point peut être sujet à débats, et je serai ravis d'avoir
des retours d'expérience en commentaire.&lt;/p&gt;
&lt;p&gt;Je l'aborde car j'ai une expérience des 2 situations.&lt;/p&gt;
&lt;p&gt;Il nous arrive parfois d'être sollicité par des prospects qui mettent également
en balance des freelances.&lt;/p&gt;
&lt;p&gt;C'est tout à fait normal que le client multiplie les pistes afin de faire son choix ;
même si, à mon sens, solliciter plus de 5 intervenants s'apparente à un manque de respect
au vu du temps qu'ils auront chacun à consacrer.&lt;/p&gt;
&lt;p&gt;Toutefois, je pense que solliciter à la fois des freelances et des agences n'a pas de sens.&lt;/p&gt;
&lt;p&gt;Selon moi la vocation du freelance est de proposer son expertise dans une thématique, qu'elle soit
technique ou non.&lt;/p&gt;
&lt;p&gt;Contracter avec un freelance pour adresser plusieurs thématiques est un problème à la fois
pour le freelance mais également pour le client.&lt;/p&gt;
&lt;p&gt;À l'inverse, l'agence a vocation à accompagner un client dans plusieurs domaines, de la spécification d'un besoin
à la mise en ligne d'un projet.&lt;/p&gt;
&lt;p&gt;Le risque pour un freelance me semble de devenir un mouton à 5 pattes, ou d'accepter de le devenir.
Le risque pour une agence me semble d'adresser trop de domaines en devenant moyenne dans chacun d'eux.&lt;/p&gt;
&lt;h3&gt;Je suis client, de quoi ai je besoin ?&lt;/h3&gt;
&lt;p&gt;Vous avez besoin d'un intervenant sur une thématique précise et bornée ? Les freelances sont excellents
dans ce périmètre.&lt;/p&gt;
&lt;p&gt;Gardez toutefois en tête que les disponibilités d'un freelance sont variables, la qualité du livrable
et sa documentation sont des éléments importants à prendre en considération dans une éventualité de reprise.&lt;/p&gt;
&lt;p&gt;Et ce qui intéresse le freelance, au delà de sa liberté (relative), c'est de multiplier les expériences
et les challenges, autrement il se ferait embaucher par le client final.&lt;/p&gt;
&lt;p&gt;Vous avez besoin d'accompagnement sur la durée ? C'est plutôt l'objectif d'une agence qui réalise souvent
des prestations plus longues.&lt;/p&gt;
&lt;p&gt;Vous avez besoin d'une force de frappe plus conséquente pour réaliser votre projet ? C'est également là que
l'agence peut se démarquer.&lt;/p&gt;
&lt;p&gt;Un freelance a des contraintes autres que votre projet: gestion administrative, prospection, suivi client, veille…
qu'il peut difficilement mutualiser ou reporter, autrement il ne tiendra pas sur le long terme.&lt;/p&gt;
&lt;p&gt;Ces responsabilités existent aussi dans une agence mais peuvent être diluées entre plusieurs personnes,
ce qui entraîne une disponibilité plus importante dans la réalisation effective de votre projet.&lt;/p&gt;
&lt;p&gt;Cette disponibilité n'est pas nécessairement problématique mais doit être abordée dès le départ.
Elle peut se régler en plusieurs configurations: disponible 2 semaines de suite mais pas la 3ème, disponible 4J sur 5 ; tout dépend de vos contraintes et vos besoins en tant que client.&lt;/p&gt;
&lt;p&gt;Il est toujours possible de réunir plusieurs freelances aux compétences différentes et sensées être complémentaires.
Dans les faits, j'ai rarement vu de réussite dans ces configurations. D'une part, cela oblige le client
à orchestrer et organiser tout ce petit monde.&lt;/p&gt;
&lt;p&gt;D'autre part, cela dilue les responsabilités où chacun a vite fait de se défausser sur l'autre (c'est pas la faute
du créa, c'est la faute de l'inté…) et d'allonger les délais (je ne peux pas avancer tant que je n'ai pas reçu…).&lt;/p&gt;
&lt;p&gt;Dans le cadre d'une agence cette organisation est masquée et n'est pas de votre ressort, ce qui ne signifie
pas que ces problématiques n'existent pas.&lt;/p&gt;
&lt;h3&gt;Et le prix ?&lt;/h3&gt;
&lt;p&gt;Soyons clair le prix n'a rien à voir avec le type de structure, il dépend de la valeur qu'une personne
ou une structure peut apporter à votre projet.&lt;/p&gt;
&lt;p&gt;Ça semble idiot à écrire mais les clients se posent rarement ces questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Est ce que la personne ou l'agence a de l'expérience dans le domaine qui m'intéresse ?&lt;/li&gt;
&lt;li&gt;Est elle référente dans son domaine de compétence ?&lt;/li&gt;
&lt;li&gt;Est elle comparable ou est elle trop opaque pour que j'ai le moindre critère objectif ?&lt;/li&gt;
&lt;li&gt;A t-elle déjà travaillé sur des sujets proches ?&lt;/li&gt;
&lt;li&gt;Que disent les gens qui ont travaillé avec ?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Votre projet n'a pas besoin de valoir 100K€ pour se poser ces questions.&lt;/p&gt;
&lt;p&gt;Trop souvent on s'arrête à un prix qui ne veut rien dire. Quelqu'un vous propose un tarif de 600€/J contre 400 d'un autre côté ?
Mais si le premier réalise la prestation en 5J et l'autre en 10 ?&lt;/p&gt;
&lt;p&gt;Sollicitez des experts sur le cœur de valeur de votre projet, qu'ils soient freelance ou agence et faites en sorte de
le faire en premier.&lt;/p&gt;
&lt;p&gt;Vous construirez la partie visible de l'iceberg par la suite, sans forcément avoir besoin de spécialistes
pour ça.&lt;/p&gt;
&lt;p&gt;Nous avons des clients qui nous sollicitent pour prototyper le cœur de l'application uniquement et cela nous convient très bien. D'autre souhaitent un accompagnement qui va au delà de la réalisation et cela nous plaît également.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Prestataires : que vous travailliez seul ou en équipe, bâtissez votre offre sur vos points forts quels qu'ils soient (compétences techniques,
réseau, communication…) et prenez le risque de dire non quand vous sentez que vous n'allez pas dans le bon sens.&lt;/p&gt;
&lt;p&gt;Clients : impliquez vous ! Dans votre produit cela semble évident, mais essayez également de comprendre, sans nécessairement
maîtriser, les domaines de compétences auxquels vous faites appel.&lt;/p&gt;
&lt;p&gt;C'est cette implication qui vous permettra de faire les bons choix d'intervenants et qui conditionne
la réussite de votre projet.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Les filles apprennent à développer à Lille le 31 janvier et 01 février 2014 à Lille avec RailsGirls Lille</title>
    <id>https://martin.catty.fr/blog/les-filles-apprennent-a-developper-a-lille-le-31-janvier-et-01-fevrier-2014-a-lille-avec-railsgirls-lille/</id>
    <link href="https://martin.catty.fr/blog/les-filles-apprennent-a-developper-a-lille-le-31-janvier-et-01-fevrier-2014-a-lille-avec-railsgirls-lille/" rel="alternate"/>
    <updated>2014-01-20T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Geeks cherchent geekettes&lt;/h3&gt;
&lt;p&gt;L'idée de RailsGirls est simple : permettre aux filles (mais pas que) de venir gratuitement s'initier
au développement web le temps d'une journée.&lt;/p&gt;
&lt;p&gt;C'est un concept né en 2010 en Finlande.&lt;/p&gt;
&lt;p&gt;Il ne s'agit pas de chercher à devenir développeuse au sortie d'un RailsGirls (même si on espère créer
des vocations), mais surtout de permettre aux filles de mieux appréhender le métier technique de développement et venir
poser toutes leurs questions, surtout celles que l'on ose pas poser à ses collègues habituellement.&lt;/p&gt;
&lt;h3&gt;C'est pour qui ?&lt;/h3&gt;
&lt;p&gt;Le Rails Girls s'organise en 3 types de participants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les mentors&lt;/li&gt;
&lt;li&gt;Les initiés&lt;/li&gt;
&lt;li&gt;Les sponsors, dont Synbioz fait partie&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;C'est quand ?&lt;/h3&gt;
&lt;p&gt;Rails Girls Lille aura lieu le vendredi 31 janvier 2014 (soir) et le samedi 01 février 2014 (journée).&lt;/p&gt;
&lt;p&gt;L'événement s'organise en deux temps, la veille chacun vient avec sa machine, on se regroupe
autour d'un apéro, on apprend à se connaître et on configure les machines pour être prêt à l'emploi le lendemain.&lt;/p&gt;
&lt;h3&gt;C'est où ?&lt;/h3&gt;
&lt;p&gt;Les 2 jours se dérouleront à Lille, au &lt;a href="https://goo.gl/maps/NDZaA"&gt;Mutualab, 19 rue Nicolas Leblanc Lille&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;On s'inscrit où ?&lt;/h3&gt;
&lt;p&gt;L'inscription se fait via meetup, à cette adresse: &lt;a href="http://www.meetup.com/rubynord/events/159579892/"&gt;http://www.meetup.com/rubynord/events/159579892/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Vous pouvez retrouver toutes les informations concernant l'événement sur la &lt;a href="http://railsgirls.com/lille"&gt;page Rails Girls dédiée&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Je ne viendrai pas mais…&lt;/h3&gt;
&lt;p&gt;Même si vous ne venez pas vous pouvez nous aider !&lt;/p&gt;
&lt;p&gt;Il vous suffit de relayer le message, que ça soit sur Facebook, Twitter, en envoyant un email à vos amis
ou tout autre moyen.&lt;/p&gt;
&lt;p&gt;Rendez vous dans 10 jours.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Du cache HTTP avec les ETag en rails</title>
    <id>https://martin.catty.fr/blog/du-cache-http-avec-les-etag-en-rails/</id>
    <link href="https://martin.catty.fr/blog/du-cache-http-avec-les-etag-en-rails/" rel="alternate"/>
    <updated>2013-12-06T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Notre article sur &lt;a href="http://www.synbioz.com/blog/les_caches_http"&gt;les caches http&lt;/a&gt; vous a
expliqué comment fonctionnait le mécanisme de cache, et les échanges entre client et serveur associés.&lt;/p&gt;
&lt;p&gt;Nous y avions également évoqué le fonctionnement du mécanisme d'ETag.
Voyons aujourd'hui comment le mettre en place dans notre application ruby on rails.&lt;/p&gt;
&lt;h3&gt;À quoi sert un ETag ?&lt;/h3&gt;
&lt;p&gt;Le principe d'un ETag est d'associer un identifiant à une ressource HTTP, typiquement une URL.
Généralement cet identifiant correspond à un hash.&lt;/p&gt;
&lt;p&gt;Ce hash est relatif au contenu que sert la ressource HTTP, de telle sorte qu'il change
si la ressource fournie change également.&lt;/p&gt;
&lt;p&gt;Quand le client va interroger le serveur il va demander l'URL en fournissant également le ETag qu'il possède. Le serveur va vérifier que le ETag fourni correspond toujours à la ressource actuelle. Si c'est le cas, il va simplement renvoyer un code 304 pour lui signifier qu'il peut utiliser
sa version en cache car elle est à jour.&lt;/p&gt;
&lt;p&gt;Autrement il fournit le nouvel ETag et son contenu.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/etag.png"&gt;&lt;img alt="Gestion des ETag" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/etag_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;On veut des exemples&lt;/h3&gt;
&lt;p&gt;Nous allons créer une application simple basée sur un scaffold de produit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bin/rails&lt;span class="w"&gt; &lt;/span&gt;g&lt;span class="w"&gt; &lt;/span&gt;scaffold&lt;span class="w"&gt; &lt;/span&gt;products&lt;span class="w"&gt; &lt;/span&gt;title&lt;span class="w"&gt; &lt;/span&gt;description:text&lt;span class="w"&gt; &lt;/span&gt;price:decimal&lt;span class="w"&gt; &lt;/span&gt;active:boolean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Voilà la tête de notre action show:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;class&lt;span class="w"&gt; &lt;/span&gt;ProductsController&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;ApplicationController
&lt;span class="w"&gt;  &lt;/span&gt;before_action&lt;span class="w"&gt; &lt;/span&gt;:set_product,&lt;span class="w"&gt; &lt;/span&gt;only:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;:show,&lt;span class="w"&gt; &lt;/span&gt;:edit,&lt;span class="w"&gt; &lt;/span&gt;:update,&lt;span class="w"&gt; &lt;/span&gt;:destroy&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# GET /products/1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# GET /products/1.json&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;show
&lt;span class="w"&gt;  &lt;/span&gt;end
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dès lors que vous avez en place un contrôleur classique, un entête ETag est déjà envoyé
par le serveur avec votre réponse, par contre il n'est pas exploité en l'état.&lt;/p&gt;
&lt;p&gt;Pour mettre en place la gestion du ETag c'est aussi simple que :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;fresh_when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dès lors notre contrôleur va renvoyer une 304 si la ressource n'a pas changée.&lt;/p&gt;
&lt;h3&gt;Un cache lié à l'objet&lt;/h3&gt;
&lt;p&gt;L'intérêt est que vous n'avez pas à gérer manuellement l'invalidation du cache et la gestion du ETag.
En effet dès lors que vous modifiez votre objet, par exemple :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;
&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_attributes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;le ETag va changer, le serveur enverra à nouveau une 200 et votre nouvelle ressource sera cachée
par le client.&lt;/p&gt;
&lt;p&gt;Ce système est vraiment pratique, car transparent, mais il a ses défauts. D'une part si vous
mettez à jour l'objet directement en base, que ça soit par le biais d'un script tiers,
ou d'un &lt;code&gt;update_column(s)&lt;/code&gt; le ETag associé à votre objet ne changera pas.&lt;/p&gt;
&lt;p&gt;D'autre part ce couplage entre ETag et objet ne permet pas d'invalider le cache
en cas de modification de la vue.&lt;/p&gt;
&lt;p&gt;Si par exemple vous modifiez une balise, cela ne sera pas pris en compte.&lt;/p&gt;
&lt;h3&gt;Aller plus loin&lt;/h3&gt;
&lt;h4&gt;Réaliser des opérations selon l'état du cache&lt;/h4&gt;
&lt;p&gt;Si vous souhaitez réaliser des traitements si votre objet est périmé vous pouvez utiliser &lt;code&gt;stale?&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stale?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute_values&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;respond_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Que ce soit &lt;code&gt;stale?&lt;/code&gt; ou &lt;code&gt;fresh_when&lt;/code&gt; vous pouvez passer 3 options, le &lt;code&gt;etag&lt;/code&gt;, si vous voulez spécifier sur quel objet le calculer, le &lt;code&gt;last_modified&lt;/code&gt;, qui par défaut utilisera le &lt;code&gt;updated_at&lt;/code&gt;
de l'objet s'il existe et enfin &lt;code&gt;public&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Par défaut le cache est privé, ce qui empêche les intermédiaires entre client et serveur
de mettre en cache la ressource (un proxy par exemple).&lt;/p&gt;
&lt;p&gt;Si votre ressource HTTP ne contient que des informations publiques vous pouvez l'utiliser.&lt;/p&gt;
&lt;h4&gt;ETag global&lt;/h4&gt;
&lt;p&gt;Depuis Rails 4 vous pouvez spécifier un bloc pour l'ensemble de votre contrôleur.&lt;/p&gt;
&lt;p&gt;C'est très commode dans le cas ou vos URL renvoient des contenus différents selon les cas.
L'exemple le plus fréquent est celui de la gestion de l'utilisateur courant.&lt;/p&gt;
&lt;p&gt;Sur les différentes actions, on voudra générer un ETag différent d'un utilisateur à l'autre afin de s'assurer qu'un utilisateur non identifié ne puisse accéder à une page en cache.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Il est parfaitement possible de cumuler les bloc &lt;code&gt;etag {}&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;%w(index edit)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Comment sont gérés les ETag par Rails ?&lt;/h3&gt;
&lt;p&gt;Tout d'abord il faut préciser que la gestion est différente entre Rails 3 et Rails 4.&lt;/p&gt;
&lt;p&gt;En rails 3 c'est un hash du body qui était produit en tant que contenu du ETag.
L'avantage est que la modification de la vue était bien répercutée sur le ETag,
on pouvait donc en modifier le code sans problème.&lt;/p&gt;
&lt;p&gt;L'inconvénient était de devoir recalculer ce body à chaque fois. Au final on gagnait
en données non transférées mais le serveur devait toujours fournir le même travail.&lt;/p&gt;
&lt;p&gt;En Rails 4 en revanche, quand nous passons notre objet product à &lt;code&gt;fresh_when&lt;/code&gt;, rails va faire l'équivalent de:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@product&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_modified&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le setter etag peut prendre un tableau, qui correspond aux blocs définis dans le contrôleur.&lt;/p&gt;
&lt;p&gt;Il faut savoir que chaque objet à sa propre clé de cache, que ce soit l'instance
d'ActiveRecord, comme le modèle en lui même:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache_key&lt;/span&gt;
&lt;span class="c1"&gt;# products&lt;/span&gt;
&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache_key&lt;/span&gt;
&lt;span class="c1"&gt;# "products/1-20131206092118622798000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La chaîne de &lt;code&gt;product&lt;/code&gt; correspond donc à la clé de cache du modèle suivi de l'id et du timestamp (colonne &lt;code&gt;updated-at&lt;/code&gt; en UTC) de l'objet.&lt;/p&gt;
&lt;p&gt;Enfin cette clé est hashée en md5:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MD5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# f8ea69ebc103b48971a6ec1bd82af7ef&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Rails 4 se base donc uniquement sur la clé de l'objet et non plus sur le contenu de la réponse.
C'est beaucoup plus performant mais ça pose les problèmes de modification de la vue.&lt;/p&gt;
&lt;h4&gt;Et pour les collections ?&lt;/h4&gt;
&lt;p&gt;Vous vous demandez sûrement comment Rails fonctionne pour les collections.
En effet il n'y a pas de &lt;code&gt;cache_key&lt;/code&gt; pour &lt;code&gt;ActiveRecord::Relation&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Et bien rails, plus particulièrement &lt;code&gt;ActiveSupport::Cache.expand_cache_key(object)&lt;/code&gt;,
renvoie une concaténation des différentes clés des objets composant la collection.&lt;/p&gt;
&lt;p&gt;Dans ce cas, si votre collection est triée dans votre vue, vous aurez besoin d'utiliser
vos paramètres de tri dans un bloc &lt;code&gt;etag {}&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Le gros avantage du cache HTTP par le biais des ETag tient au fait qu'il est disponible de base.
Vous n'avez pas d'installation complémentaire à réaliser.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://fr.wiktionary.org/wiki/Captain_Obvious"&gt;Forcément, plus la page de base est lourde, plus ce cache est important&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Maintenant, comme tout système de cache, il doit être bien réfléchi et il sera relativement complexe à tester.&lt;/p&gt;
&lt;blockquote&gt;
  There are only two hard things in Computer Science: cache invalidation and naming things.
  &lt;footer&gt;
    —
    &lt;cite&gt;Tim Bray.&lt;/cite&gt;
  &lt;/footer&gt;
&lt;/blockquote&gt;</content>
  </entry>
  <entry>
    <title>Créer un nouvel utilisateur PostgreSQL</title>
    <id>https://martin.catty.fr/blog/creer-un-nouvel-utilisateur-postgresql/</id>
    <link href="https://martin.catty.fr/blog/creer-un-nouvel-utilisateur-postgresql/" rel="alternate"/>
    <updated>2013-10-07T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Créer utilisateur PostgreSQL&lt;/h3&gt;
&lt;p&gt;Lorsqu'on vient d'un autre SGBD on ne sait pas forcément comment
fonctionne PostgreSQL pour la création d'utilisateur.&lt;/p&gt;
&lt;p&gt;En fait il possède des commandes et un utilisateur dédié.
La première chose à faire va donc être de basculer sur cet utilisateur.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;postgres
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ensuite nous allons créer un nouvel utilisateur pour notre projet.&lt;/p&gt;
&lt;p&gt;Il est toujours mieux d'isoler les rôles et de limiter la portée
d'une base à un utilisateur.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;createuser&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-P&lt;span class="w"&gt; &lt;/span&gt;martin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;L'option &lt;code&gt;-d&lt;/code&gt; va permettre à notre utilisateur de créer des bases.
L'option &lt;code&gt;-P&lt;/code&gt; va forcer l'affectation d'un mot de passe qui nous
sera demandé de manière interactive.&lt;/p&gt;
&lt;h3&gt;Créer la base de donnée&lt;/h3&gt;
&lt;p&gt;Nous allons maintenant créer la base de données en spécifiant
que c'est mon utilisateur qui en est le possesseur (owner), d'où le &lt;code&gt;-O&lt;/code&gt;.
Cette opération se fait toujours depuis le shell postgres.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;createdb&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;martin&lt;span class="w"&gt; &lt;/span&gt;bdd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Connexion à la base de données PostgreSQL&lt;/h3&gt;
&lt;p&gt;Nous pouvons maintenant sortir du shell postgres et nous
connecter avec notre utilisateur:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;psql&lt;span class="w"&gt; &lt;/span&gt;-U&lt;span class="w"&gt; &lt;/span&gt;martin&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;bdd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;À noter qu'il est possible de réaliser toutes ces opérations
directement en SQL mais il est préférable d'utiliser les commandes dédiées.&lt;/p&gt;
&lt;p&gt;En cas de doute n'hésitez pas utiliser &lt;code&gt;--help&lt;/code&gt; sur les commandes précédentes.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>respond_to et Ruby on Rails</title>
    <id>https://martin.catty.fr/blog/respond_to-et-ruby-on-rails/</id>
    <link href="https://martin.catty.fr/blog/respond_to-et-ruby-on-rails/" rel="alternate"/>
    <updated>2013-09-18T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Dans notre série d'articles sur les &lt;a href="http://www.synbioz.com/bases-ruby-on-rails"&gt;bases de Ruby on Rails&lt;/a&gt;,
explorons aujourd'hui la magie de respond_to.&lt;/p&gt;
&lt;h3&gt;À quoi sert respond_to ?&lt;/h3&gt;
&lt;p&gt;Le mécanisme &lt;code&gt;respond_to&lt;/code&gt; est présent depuis les toutes premières versions de Ruby on Rails.&lt;/p&gt;
&lt;p&gt;Il permet depuis un contrôleur d'offrir des réponses différentes selon le
format demandé.&lt;/p&gt;
&lt;p&gt;Par exemple si je demande l'affichage de mes utilisateurs en JSON, XML ou HTML,
le format de rendu sera différent.&lt;/p&gt;
&lt;p&gt;C'est particulièrement pratique si votre application web possède un
front (visible en HTML) mais qu'elle agit également comme une API,
par exemple au format JSON pour interagir avec une application mobile.&lt;/p&gt;
&lt;h3&gt;Comment est déterminé le format ?&lt;/h3&gt;
&lt;p&gt;Prenons un contrôleur tout ce qu'il y a de plus basique:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ProductsController&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ApplicationController&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ruby on Rails utilise 2 moyens pour déterminer le format demandé.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Depuis l'extension de l'url (ex: /products.xml)&lt;/li&gt;
&lt;li&gt;Depuis les entêtes envoyés par le navigateur (entête Accept)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le premier est prioritaire sur le second, vérifions avec &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8000/products.xml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Voici la partie du résultat qui nous intéresse:&lt;/p&gt;
&lt;pre&gt;
Missing template products/index, application/index with {:locale=&amp;gt;[:en], :formats=&amp;gt;[:xml], :handlers=&amp;gt;[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}. Searched in:
  * &amp;quot;/Users/fuse/tmp/respond/app/views&amp;quot;
&lt;/pre&gt;

&lt;p&gt;On voit bien que le format recherché est XML. Ce format n'existe d'ailleurs pas de notre côté.&lt;/p&gt;
&lt;p&gt;À noter que le contrôleur n'a pas besoin de &lt;code&gt;respond_to&lt;/code&gt; explicite quand la vue est présente,
quelque soit le format.&lt;/p&gt;
&lt;p&gt;Si nous créons une fichier &lt;code&gt;views/products/index.html.erb&lt;/code&gt;,  ou &lt;code&gt;views/products/index.xml&lt;/code&gt;…
le contrôleur rendra automatiquement la vue correspondante au format, autrement nous
aurons un &lt;code&gt;Template is missing&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Toutefois dès lors que nous définissons un bloc &lt;code&gt;respond_to&lt;/code&gt;, nous devons définir
tous les formats acceptés, le mapping ne se fait plus automatiquement avec les vues.&lt;/p&gt;
&lt;h3&gt;Gestion des formats&lt;/h3&gt;
&lt;p&gt;Vous aurez remarqué que Rails «connaît» un certain nombre de formats.
Ces formats représentent le content-type du fichier, autrement appelé type MIME.
Vous pouvez les lister avec &lt;code&gt;Mime::EXTENSION_LOOKUP&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dans Rails, chaque type est en fait un sous module de MIME.
Exemple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Mime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CSV&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#&amp;lt;Mime::Type:0x007f8954b5e3b8 @synonyms=[], @symbol=:csv, @string="text/csv"&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Si dans une action de votre contrôleur vous n'avez pas de &lt;code&gt;respond_to&lt;/code&gt; et que vous demandez par exemple &lt;code&gt;products.foo&lt;/code&gt;,
c'est le format HTML qui sera utilisé, et ce même si vos entêtes demandent un format qui existe.&lt;/p&gt;
&lt;p&gt;C'est tout à fait normal car les entêtes envoyés par le navigateur
&lt;a href="http://blog.noremember.org/index.php?post/2009/10/12/Internet-Explorer-accepte-n-importe-quoi"&gt;peuvent contenir à peu près n'importe quoi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;curl -i -H "Accept: application/xml" http://localhost:8000/products.foo&lt;/code&gt;
renverra donc la liste des produits en HTML, même si le fichier index.xml est présent.&lt;/p&gt;
&lt;h3&gt;Définir vos propres formats&lt;/h3&gt;
&lt;p&gt;Définir votre propre format est trivial, un initializer est prévu pour cela,
&lt;code&gt;config/initializers/mime_types.rb&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Mime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.ms-excel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:xls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;À partir de là vous pourrez gérer votre format Excel dans votre bloc &lt;code&gt;respond_to&lt;/code&gt;.
Si vous avez besoin de manipuler le format, celui ci se trouve directement
dans la requête (&lt;code&gt;request.format&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Exemple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;respond_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Charge à notre code d'implémenter le to_xls&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;xls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_xls&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Un mot sur respond_with&lt;/h3&gt;
&lt;p&gt;Pour simplifier un peu plus Ruby on Rails a introduit le mécanisme
&lt;code&gt;respond_with&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Il s'agit de définir au niveau contrôleur les formats auxquels
on répond, puis de répondre avec un objet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ProductsController&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ApplicationController&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;respond_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:xml&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;respond_with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@products&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Par défaut Rails utilise le builder intégré, c'est à dire
que dans le cas de XML il fera un rendu de &lt;code&gt;@products.to_xml&lt;/code&gt;,
mais il parfaitement possible de définir sa propre vue.&lt;/p&gt;
&lt;p&gt;À noter que les formats qui ne sont pas explicitement spécifiés
ne sont pas accessibles. Ici l'URL &lt;code&gt;/products&lt;/code&gt; n'affichera pas la version HTML.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Exploiter des données avec HStore et Ruby on Rails</title>
    <id>https://martin.catty.fr/blog/exploiter-des-donnees-avec-hstore-et-ruby-on-rails/</id>
    <link href="https://martin.catty.fr/blog/exploiter-des-donnees-avec-hstore-et-ruby-on-rails/" rel="alternate"/>
    <updated>2013-08-21T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;À l'heure (la mode) du Big Data, l'exploitation de données a le vent en poupe.&lt;/p&gt;
&lt;p&gt;Aujourd'hui, quand on pense exploitation de données volumineuses et pas forcément structurées,
on pense aussi généralement NoSQL.&lt;/p&gt;
&lt;p&gt;Pourtant, des bases de données relationnelles comme PostgreSQL offrent des outils
très intéressants tels que l'extension HStore.&lt;/p&gt;
&lt;p&gt;Cerise sur le gâteau, notre framework préféré, Ruby on Rails, gère HStore
via ActiveRecord depuis la version 4.&lt;/p&gt;
&lt;h3&gt;Présentation de HStore&lt;/h3&gt;
&lt;p&gt;À l'heure actuelle, pour stocker des données non structurées on pouvait utiliser
le serialize de Ruby on Rails. Cette solution offre l'avantage d'être indépendante
du SGBD.&lt;/p&gt;
&lt;p&gt;De son côté HStore est une extension de PostgreSQL permettant de stocker des données
non structurées sous la forme clé / valeur dans une colonne d'une table classique.
Cette colonne est donc de type HStore.&lt;/p&gt;
&lt;p&gt;Contrairement à un outil comme redis, ce store peut être interrogé
en SQL. Il est par contre dépendant de PostgreSQL.&lt;/p&gt;
&lt;h3&gt;Usage du HStore&lt;/h3&gt;
&lt;p&gt;Voici notre cas d'usage: nous avons plusieurs fichiers CSV contenant des informations utilisateurs
sur lesquelles nous aimerions réaliser quelques traitements.&lt;/p&gt;
&lt;p&gt;Par exemple, nous voudrions faire une jointure de ces deux fichiers pour ne conserver
que les lignes avec des emails communs.&lt;/p&gt;
&lt;p&gt;D'un CSV à l'autre les données ne sont pas forcément structurées de la même façon (ordre des colonnes…).&lt;/p&gt;
&lt;h3&gt;Création de l'application&lt;/h3&gt;
&lt;p&gt;En partant de rails 4, générons une application et configurons la pour utiliser &lt;code&gt;pg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cette application est disponible sur &lt;a href="https://github.com/synbioz/datamining"&gt;notre github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;HStore étant une extension, nous allons l'activer avec une migration.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bin/rails g migration enable_hstore&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;SetupHstore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;up&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;enable_extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hstore"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;down&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;disable_extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hstore"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nous allons ensuite créer un modèle représentant notre datastore.&lt;/p&gt;
&lt;p&gt;Ce modèle ne comportera qu'une colonne, stockant le path de notre fichier CSV.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bin/rails g model datastore path:string&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;CreateDatastores&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:datastores&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:path&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamps&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nous allons maintenant créer un modèle &lt;code&gt;Line&lt;/code&gt; contenant les différents enregistrements,
lié à un &lt;code&gt;Datastore&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;datastore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;references&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;hstore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;CreateLines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:lines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;references&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:datastore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hstore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:data&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamps&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notre modèle &lt;a href="https://github.com/synbioz/datamining/blob/master/app/models/datastore.rb"&gt;datastore&lt;/a&gt; possède une méthode de classe
pour créer un datastore depuis un path.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'csv'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Datastore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;has_many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:lines&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Load datastore from CSV file.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# @param  path [String] path to the file. Must be related to rails root.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# @return [Datastore] return the loaded datastore.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Datastore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;datas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;datas&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Return duplicated lines of the store, base on the key.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# @param  store [Datastore] Datastore to compare with&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# @param  key [String] Key for comparing value&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# @return [Array] Lines&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;duplicated_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"join lines as l on l.datastore_id = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lines.data -&amp;gt; :key = l.data -&amp;gt; :key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Il n'est pas recommandé de lire tout le fichier CSV en mémoire,
ici je le fais parce que le nombre de lignes n'est pas très important.&lt;/p&gt;
&lt;p&gt;Une fois lu, j'extrais les headers, puis j'itère sur les lignes que
je &lt;code&gt;zip&lt;/code&gt; avec ces headers.&lt;/p&gt;
&lt;p&gt;Le &lt;code&gt;zip&lt;/code&gt; va nous permettre de mixer les éléments deux à deux ;
puis nous transformons le tableau résultant en hash pour le stocker dans le HStore.&lt;/p&gt;
&lt;p&gt;À noter que le code contient 2 CSV dans le répertoire &lt;code&gt;datas&lt;/code&gt;
pour nos tests. Les tests unitaires sont dans &lt;code&gt;test/models&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;C'est la méthode &lt;code&gt;duplicated_lines&lt;/code&gt; qui nous intéresse le plus car elle illustre
ce que nous pouvons faire avec HStore.&lt;/p&gt;
&lt;p&gt;Nous allons faire une jointure de la table ligne sur elle même en
filtrant sur la clé.&lt;/p&gt;
&lt;p&gt;Nous retournons le tout sous forme d'un tableau de lignes.&lt;/p&gt;
&lt;p&gt;Hormis le requêtage légèrement différent, le reste est tout à fait classique.&lt;/p&gt;
&lt;p&gt;Voici quelques exemples de requêtes possibles:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# nombre de lignes qui n'ont pas la clé email&lt;/span&gt;
&lt;span class="no"&gt;Line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data ? :key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;

&lt;span class="c1"&gt;# ligne avec des emails en .us&lt;/span&gt;
&lt;span class="no"&gt;Line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data -&amp;gt; :key LIKE :value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'%.us'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Le gros avantage du HStore est d'être à la fois souple et performant.&lt;/p&gt;
&lt;p&gt;À l'inverse, les données sérialisées sont très lourdes à manipuler,
et même si cela reste simple de les comparer en ruby, c'est bien plus lent
que d'utiliser directement PostgreSQL pour cela.&lt;/p&gt;
&lt;p&gt;Dernier point, et non des moindre, il est parfaitement possible d'indexer les
champs de type HStore pour en accélérer le requêtage. Le gain n'est effectif
que sur certains opérateurs.&lt;/p&gt;
&lt;p&gt;Pour aller plus loin, je vous encourage à consulter l'excellente &lt;a href="http://www.postgresql.org/docs/9.2/static/hstore.html"&gt;documentation
en ligne de PostgreSQL&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Un asset pipeline pour sinatra</title>
    <id>https://martin.catty.fr/blog/un-asset-pipeline-pour-sinatra/</id>
    <link href="https://martin.catty.fr/blog/un-asset-pipeline-pour-sinatra/" rel="alternate"/>
    <updated>2013-07-16T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Ruby on Rails nous offre de précieux outils par défaut pour améliorer
la performance de nos applications web.&lt;/p&gt;
&lt;p&gt;L'asset pipeline en fait partie. En suivant quelques conventions,
les fichiers JavaScript et CSS sont automatiquement compressés
et minifiés en production.&lt;/p&gt;
&lt;h3&gt;Sprockets&lt;/h3&gt;
&lt;p&gt;Ce travail est réalisé par la gem &lt;a href="https://github.com/sstephenson/sprockets"&gt;sprockets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Toutefois Sprockets est une application Rack à part entière et est maintenant
complètement décorrélée de Ruby on Rails.&lt;/p&gt;
&lt;p&gt;Rien ne nous empêche donc de l'utiliser dans un autre framework basé sur Rack
comme Sinatra.&lt;/p&gt;
&lt;h3&gt;Intégration de Sprockets dans Sinatra&lt;/h3&gt;
&lt;p&gt;Sinatra est le micro framework que nous utilisons pour &lt;a href="http://www.synbioz.com/blog/les_technologies_derriere_synbioz_com"&gt;faire tourner
ce site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour de petits besoins applicatifs il est parfaitement adapté.&lt;/p&gt;
&lt;p&gt;Pour commencer nous allons charger les gems qui vont bien :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'sprockets'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'yui-compressor'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;yui-compressor&lt;/code&gt; va se charger de compresser le résultat en sortie.&lt;/p&gt;
&lt;p&gt;Puis nous éditons notre fichier de rackup :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'sprockets'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yui/compressor"&lt;/span&gt;

&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/assets'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Sprockets&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'assets/javascripts'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'assets/stylesheets'&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js_compressor&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;YUI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;JavaScriptCompressor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css_compressor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;YUI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CssCompressor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ensuite il reste à créer une arborescence &lt;code&gt;assets&lt;/code&gt; avec les sous-dossiers
&lt;code&gt;javascripts&lt;/code&gt; et &lt;code&gt;stylesheets&lt;/code&gt; et d'y placer les manifest et les fichiers.&lt;/p&gt;
&lt;p&gt;On pourra également créer un sous dossier &lt;code&gt;vendor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Un exemple de manifest:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sr"&gt;//&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;
&lt;span class="sr"&gt;//&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;C'est tout ce dont vous aurez besoin pour que vos assets soient servis
compressés et minifiés.&lt;/p&gt;
&lt;p&gt;Le problème est que cette compilation va se faire à chaud (à l'éxécution),
ce qui n'est pas particulièrement performant et donc pas acceptable en production.&lt;/p&gt;
&lt;p&gt;L'idéal serait que, comme en Rails, un fichier soit compilé au déploiement
et automatiquement servi.&lt;/p&gt;
&lt;h3&gt;Compiler les assets&lt;/h3&gt;
&lt;p&gt;Commencons par définir sprockets dans le fichier de notre application,
pour nous &lt;code&gt;synbioz.rb&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;APP_DIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pwd&lt;/span&gt;

&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:sprockets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Sprockets&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;APP_DIR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;STDOUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:assets_prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'compiled'&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:assets_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;APP_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assets_prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sprockets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'assets/javascripts'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sprockets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'assets/stylesheets'&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sprockets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js_compressor&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;YUI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;JavaScriptCompressor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sprockets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css_compressor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;YUI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CssCompressor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nous allons maintenant adapter le bloc &lt;code&gt;/assets&lt;/code&gt; de notre rackup, qui
sera utilisé uniquement en mode development.&lt;/p&gt;
&lt;p&gt;En effet les assets ne seront plus servis en invoquant directement sprockets
en production.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/assets'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sprockets&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Dorénavant il nous faut une tâche pour compiler nos manifest.
Créons cette tâche dans le Rakefile :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:assets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'compile assets'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:compile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:assets_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:compile_js&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:compile_css&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'compile javascript assets'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:compile_js&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'js'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'compile css assets'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:compile_css&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'create assets version'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:assets_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:assets_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'ASSETS_VERSION'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'w+'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assets_version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;private&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sprockets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sprockets&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sprockets&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"application.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;filetype&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;outpath&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assets_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Pathname&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outpath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"application.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assets_version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;filetype&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkdir_p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.gz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;La tâche de compilation, disponible via &lt;code&gt;rake assets:compile&lt;/code&gt; va compiler
les CSS et les JS respectivement dans &lt;code&gt;public/compiled/css&lt;/code&gt; et &lt;code&gt;public/compiled/js&lt;/code&gt;
en créant les dossiers s'ils n'existent pas.&lt;/p&gt;
&lt;p&gt;Évidemment ces dossiers ne doivent pas être versionnés.&lt;/p&gt;
&lt;p&gt;Nous créons également un fichier contenant la version des assets, sous forme d'un
timestamp, le but étant d'éviter d'avoir toujours le même nom de fichier et
que les navigateurs servent le fichier de leur cache et non le fichier à jour.&lt;/p&gt;
&lt;p&gt;Le pipeline de Rails utilise un mécanisme de digest, ici nous utilisons un simple timestamp.
Les fichiers finaux auront ce type de chemin : &lt;code&gt;public/compiled/js/application.1373894136.js.gz&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Helpers et déploiement&lt;/h3&gt;
&lt;p&gt;Il nous reste maintenant à mettre en place des helpers pour que le contenu
soit servi directement par sprockets en development et depuis les fichiers
minifiés statiques autrement.&lt;/p&gt;
&lt;p&gt;Par souci de clarté nos helpers sont encapsulés dans un module.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;AssetHelper&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@digest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ASSETS_VERSION"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;js_tag&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RACK_ENV'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'development'&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;script src="/assets/application.js" type="text/javascript"&amp;gt;&amp;lt;/script&amp;gt;'&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;script src=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;/compiled/js/application.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.js&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; type=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text/javascript&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;lt;/script&amp;gt;"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;css_tag&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RACK_ENV'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'development'&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;link rel="stylesheet" href="/assets/application.css" type="text/css" media="all" /&amp;gt;'&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;link rel=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;stylesheet&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; href=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;/compiled/css/application.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.css&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; type=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text/css&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; media=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;all&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; /&amp;gt;"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;helpers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;AssetHelper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Un point important à prendre en compte, notamment pour vos CSS : faites attention à vos chemins.&lt;/p&gt;
&lt;p&gt;Étant donné que &lt;code&gt;public/compiled/css&lt;/code&gt; possède plusieurs niveau de profondeur, les chemins
relatifs risquent d'être complexes à maintenir entre dev et production.&lt;/p&gt;
&lt;p&gt;Mes images étant servies depuis &lt;code&gt;/public&lt;/code&gt;, un chemin absolu me convient bien, autrement
un helper, type &lt;code&gt;asset_path&lt;/code&gt; en Rails, peut être pratique.&lt;/p&gt;
&lt;p&gt;Dernière chose à ne pas oublier, la compilation des assets au déploiement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;release_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; RACK_ENV=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;rack_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bin/rake assets:compile"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Et voilà, vous n'avez plus d'excuses pour faire 15 requêtes HTTP quand 2 sont possibles depuis votre site en Sinatra.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Héberger une application Ruby on Rails avec Amazon Web Services</title>
    <id>https://martin.catty.fr/blog/heberger-une-application-ruby-on-rails-avec-amazon-web-services/</id>
    <link href="https://martin.catty.fr/blog/heberger-une-application-ruby-on-rails-avec-amazon-web-services/" rel="alternate"/>
    <updated>2013-07-02T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Rubber est un plugin capistrano dont le but est de faciliter le
déploiement d'applications Ruby on Rails sur Amazon Web Services (AWS).&lt;/p&gt;
&lt;p&gt;Partons de 0 pour déployer la pré-production d'une application sur AWS.&lt;/p&gt;
&lt;h3&gt;L'application Ruby on Rails&lt;/h3&gt;
&lt;p&gt;Commencons par créer une application Rails basique:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;
&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scaffold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rake&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;migrate&lt;/span&gt;
&lt;span class="n"&gt;rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;public&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'tasks#index'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Configuration d'une instance Amazon EC2&lt;/h3&gt;
&lt;p&gt;La première chose à faire est de se créer un compte sur &lt;a href="http://aws.amazon.com/fr/"&gt;Amazon AWS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Une fois que tout est en ordre nous allons aller dans notre console,
qui reprend tous les services auxquels nous avons accès.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_console"&gt;&lt;img alt="AWS Console" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_console_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Dans la section EC2, nous allon créer une instance, en nous basant sur une Ubuntu 12.04 LTS.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_ami_1"&gt;&lt;img alt="Choix de l'AMI" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_ami_1_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nous pouvons choisir le type d'instance, selon les besoins en ressources:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_ami_2"&gt;&lt;img alt="Configuration de l'AMI" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_ami_2_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notre instance va ensuite démarrer:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_instances"&gt;&lt;img alt="Liste des instances AWS" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_instances_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nous pouvons la couper car rubber va reconfigurer sa propre instance.
Pour cela un clic droit sur la ligne avec terminate suffit.&lt;/p&gt;
&lt;h3&gt;Vérifier que notre instance Amazon EC2 fonctionne&lt;/h3&gt;
&lt;p&gt;Afin de pouvoir accèder en SSH à notre instance nous allons générer
un jeu de clés par le biais du menu key pairs.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_key_pairs"&gt;&lt;img alt="Gestion des clés AWS" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_key_pairs_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Il est possible d'utiliser le nom par défaut (gsg-keypair) ou pas.
Attention le jeu de clé doit correspondre à la zone où sera déployée l'application,
c'est à dire ne pas avoir un jeu de clé Ouest pour une application web sur la zone Est.&lt;/p&gt;
&lt;p&gt;Une fois réalisé nous allons placer cette clé privée dans le dossier ec2
et générer la clé publique associée.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;~/.ec2
mv&lt;span class="w"&gt; &lt;/span&gt;~/Downloads/gsg-keypair.pem&lt;span class="w"&gt; &lt;/span&gt;~/.ec2/gsg-keypair
chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.ec2/gsg-keypair
ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;gsg-keypair&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;gsg-keypair.pub
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Vous devez maintenant pouvoir vous connecter à l'instance via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;~/.ec2/gsg-keypair&lt;span class="w"&gt; &lt;/span&gt;ubuntu@ec2-54-218-17-153.us-west-2.compute.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;La valeur du host est le public DNS disponible quand vous cliquez sur l'instance
dans la console Amazon AWS.&lt;/p&gt;
&lt;h3&gt;Configuration de notre application Rails avec Amazon&lt;/h3&gt;
&lt;p&gt;C'est maintenant que Rubber entre en jeu.
La première chose à faire est de l'installer au niveau global.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rubber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rehash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;À l'image d'un capify pour capistrano, nous allons «vulcaniser»
l'application.&lt;/p&gt;
&lt;p&gt;Pour cela nous allons choisir un template. Les templates sont des jeux
de configuration prêts à l'emploi.&lt;/p&gt;
&lt;p&gt;Il en existe un grand nombre dans le &lt;a href="https://github.com/rubber/rubber/tree/master/templates"&gt;dépôt Github Rubber&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nous allons utiliser nginx, unicorn avec postgresql par le biais
du template &lt;code&gt;complete\_unicorn\_nginx\_postgresql&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_vulcanize"&gt;&lt;img alt="Vulcanize de l'application Ruby on Rails" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_vulcanize_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tous les fichiers de configuration sont ajoutés directement dans l'application.
Ils possèdent tous des réglages par défaut qui sont bien sûr éditables,
mais qui fonctionnent en l'état.&lt;/p&gt;
&lt;p&gt;La commande ajoute également les gem rubber et open4 dans notre Gemfile.&lt;/p&gt;
&lt;p&gt;Il est important de décommenter therubyracer dans le Gemfile, autrement aucun moteur ne sera disponible pour compiler les assets.&lt;/p&gt;
&lt;p&gt;Le seul fichier à configurer est le fichier &lt;code&gt;config/rubber/rubber.yml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Les paramètres à changer sont:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le nom de l'application&lt;/li&gt;
&lt;li&gt;L'app_user, si vous avez pris l'AMI Ubuntu c'est «ubuntu» l'utilisateur par défaut&lt;/li&gt;
&lt;li&gt;La timezone utilisée, par exemple US/Western&lt;/li&gt;
&lt;li&gt;Le domaine de l'application&lt;/li&gt;
&lt;li&gt;La région du serveur dans la section aws, par exemple us-west-2&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Configuration des clés&lt;/h4&gt;
&lt;p&gt;Il faut ensuite configurer les clés d'accès Amazon AWS.&lt;/p&gt;
&lt;p&gt;Le numéro de compte (account) se trouve dans Account Identifiers:
aws_security_credentials&lt;/p&gt;
&lt;p&gt;Il faut ensuite aller dans la section Security Credentials, puis
Access Keys et créer une clé.&lt;/p&gt;
&lt;p&gt;Vous pouvez recopier Access Key ID dans la section access_key de la configuration.
Pour le secret il faut se rendre dans la &lt;a href="https://portal.aws.amazon.com/gp/aws/securityCredentials?"&gt;section sécurité&lt;/a&gt; et l'afficher.&lt;/p&gt;
&lt;p&gt;Pour la partie keyname il faut faudra la changer si vous n'avez pas gardé &lt;code&gt;gsg-keypair&lt;/code&gt;.
Enfin il faudra configurer les informations de l'image. Le type (ex: m1.medium) et l'id (ex: ami-70f96e40).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_secret_key.png"&gt;&lt;img alt="Clés Amazon Web Services" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/aws_secret_key_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Création d'un staging&lt;/h3&gt;
&lt;p&gt;À partir de là vous avez normalement tous les réglages suffisants pour créer un staging.&lt;/p&gt;
&lt;p&gt;J'ai toutefois dû modifier les règles de sécurité suivantes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;auto_security_groups, chaque rôle a son propre groupe de sécurité.&lt;/li&gt;
&lt;li&gt;isolate_security_groups, isôle également les rôles au niveau environnement (web_production et staging_production sont des groupes différents).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;J'ai du mettre ces 2 valeurs à false pour éviter l'erreur &lt;code&gt;You have exceeded the number of VPC security groups allowed per instance. (Fog::Compute::AWS::Error)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Si vous avez des retours là dessus je suis preneur.
J'ai aussi mis &lt;code&gt;prompt_for_security_group_sync&lt;/code&gt; à false. Autrement dès lors que les groupes
de sécurité remote ne correspondent plus, il demande explicitement s'il faut bien les supprimer.&lt;/p&gt;
&lt;p&gt;Il nous reste donc à lancer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cap&lt;span class="w"&gt; &lt;/span&gt;rubber:create_staging
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Ajouter une dépendance&lt;/h3&gt;
&lt;p&gt;Imaginons que par la suite votre application ait besoin d'une dépendance supplémentaire, par exemple memcached.&lt;/p&gt;
&lt;p&gt;Dans ce cas vous allez re-vulcanizer avec le template en question:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;be cap rubber vulcanize memcached
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cela aura pour effet de créer les fichiers de configuration de memcached.&lt;/p&gt;
&lt;p&gt;Dans le fichier &lt;code&gt;config/rubber/rubber-memcached.yml&lt;/code&gt; on voit que le memcached utilise le role memcached.&lt;/p&gt;
&lt;p&gt;Notre instance de production ne possède pas encore ce rôle nous allons l'ajouter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RUBBER_ENV=production ROLES=memcached be cap rubber:roles:add
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Une fois réalisé vous pouvez relancer un bootstrap pour installer les paquets manquants sur l'instance voulue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RUBBER_ENV=production be cap rubber:bootstrap
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Il est à noter qu'avant de recréer un staging après l'avoir supprimé (&lt;code&gt;cap rubber:destroy_staging&lt;/code&gt;),
j'ai dû manuellement retirer les règles restantes dans la console AWS afin d'éviter les erreurs type:
&lt;code&gt;InvalidPermission.Duplicate =&amp;gt; the specified rule \"peer: sg-f74dac98, TCP, from port: 1, to port: 65535, ALLOW\" already exists (Fog::Compute::AWS::Error)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Autrement tout c'est déroulé correctement. Vous voilà donc avec une pré-production prêt à l'emploi en quelques commandes.&lt;/p&gt;
&lt;p&gt;Dans le prochain article nous verrons comment gérer finement plusieurs instances
pour monter en charge.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Les applications web mobiles sont elles en passe de détrôner les applications mobiles natives ?</title>
    <id>https://martin.catty.fr/blog/les-applications-web-mobiles-sont-elles-en-passe-de-detroner-les-applications-mobiles-natives/</id>
    <link href="https://martin.catty.fr/blog/les-applications-web-mobiles-sont-elles-en-passe-de-detroner-les-applications-mobiles-natives/" rel="alternate"/>
    <updated>2013-05-30T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;C'est une tendance que nous constatons, les applications web mobiles sont
de plus en plus prisées aux dépens des applications natives.&lt;/p&gt;
&lt;h3&gt;Un atout nommé responsive&lt;/h3&gt;
&lt;p&gt;L'avènement relativement récent du &lt;a href="http://www.synbioz.com/blog/responsive_web_design"&gt;responsive web design&lt;/a&gt; a beaucoup joué,
car il permet de transformer l'apparence du site en fonction des capacités
d'affichage du terminal, via les media queries de CSS 3.&lt;/p&gt;
&lt;p&gt;Cela signifie que c'est la même application web qui s'affiche que ça soit
sur ordinateur ou sur smartphone. Il n'y a donc pas de fonctionnel à re-développer.&lt;/p&gt;
&lt;p&gt;Il faut apporter une nuance sur le fait que certains composants fonctionnels,
ne peuvent pas être adaptés uniquement visuellement.&lt;/p&gt;
&lt;p&gt;Par exemple, afficher une fenêtre modale n'a pas de sens sur un mobile,
de même qu'un calendrier ne peut pas se présenter de la même façon.&lt;/p&gt;
&lt;p&gt;Cela implique de modifier l'application en elle-même. Pour autant, il n'y a qu'une
application pour les différents terminaux.&lt;/p&gt;
&lt;p&gt;Il faut également considérer que si certains éléments ne sont pas affichés,
il peuvent quand même être téléchargés, ce qui alourdit inutilement
la consommation de bande passante et qui pénalise tous les utilisateurs
et particulièrement ceux sur mobile.&lt;/p&gt;
&lt;h3&gt;Le navigateur en tant que machine virtuelle ?&lt;/h3&gt;
&lt;p&gt;Les portages d'application standalone vers le web ont été nombreux et pas toujours
heureux.&lt;/p&gt;
&lt;p&gt;Est ce qu'utiliser Photoshop dans un navigateur a du sens ? Rien n'est moins sûr.&lt;/p&gt;
&lt;p&gt;Migrer une application vers le web a du sens à partir du moment ou elle est collaborative.
Devoir lancer son navigateur pour se servir d'un outil qu'on utilise seul n'a pas de sens.&lt;/p&gt;
&lt;h4&gt;Quelles performances ?&lt;/h4&gt;
&lt;p&gt;La question de la performance a longtemps été un argument en défaveur des applications
web mobiles mais il faut reconnaître qu'elle est de moins en moins d'actualité.&lt;/p&gt;
&lt;p&gt;La puissance et les possibilités qu'offrent les navigateurs, sur mobile ou non,
sont de plus en plus impressionnantes.&lt;/p&gt;
&lt;p&gt;JavaScript est interprété un peu plus rapidement à chaque nouvelle version de navigateur,
ce qui fait du browser un excellent contexte d'exécution.&lt;/p&gt;
&lt;h4&gt;Pour quelles possibilités ?&lt;/h4&gt;
&lt;p&gt;Les navigateurs ont de plus en plus accès aux ressources intrinsèques des machines
et &lt;a href="http://www.synbioz.com/blog/introduction_a_html5"&gt;HTML 5&lt;/a&gt; a beaucoup apporté.&lt;/p&gt;
&lt;p&gt;Il suffit de se tourner vers Google, où il est maintenant possible d'effectuer une recherche
avec son micro.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Google micro" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/google_micro.png" /&gt;&lt;/p&gt;
&lt;p&gt;On peut également citer en vrac les mécanismes de stockage de données, l'accès
aux périphériques (webcam, GPS), la possibilité de faire de la visio-conférence
sans plugin (WebRTC)…&lt;/p&gt;
&lt;h3&gt;Des navigateurs homogènes sur mobile&lt;/h3&gt;
&lt;p&gt;Évidemment dès qu'on parle de fonctionnalités avancées, on se retrouve en proie
aux soucis de compatibilité des navigateurs, untel gérant X, l'autre non.&lt;/p&gt;
&lt;p&gt;Ou pire : les deux le gérant différemment.&lt;/p&gt;
&lt;p&gt;Cette contrainte est beaucoup moins présente sur le mobile que sur le desktop,
d'une part parce que le mobile est plus récent, d'autre part car les 2
principaux acteurs, Safari Mobile sur iOS et Chrome sur Android, ont longtemps
partagé une base de code commune avec WebKit (Chrome va maintenant utiliser
&lt;a href="http://www.chromium.org/blink"&gt;Blink&lt;/a&gt;)&lt;/p&gt;
&lt;h3&gt;Le web devient la plateforme&lt;/h3&gt;
&lt;p&gt;C'est la promesse de Mozilla avec &lt;a href="http://www.synbioz.com/blog/firefox_os"&gt;Firefox OS&lt;/a&gt; :
réaliser un OS Mobile entièrement avec des technologies du web.&lt;/p&gt;
&lt;p&gt;De fait, pour rendre son application disponible sur cet OS Mobile, aucune
nouvelle connaissance n'est nécessaire, ce sont les même que pour créer une
application web.&lt;/p&gt;
&lt;p&gt;Firefox OS va donc fusionner l'OS et le navigateur, limitant les couches
d'éxécution.&lt;/p&gt;
&lt;h3&gt;Quelles différences de coûts entre natif et web ?&lt;/h3&gt;
&lt;p&gt;C'est un critère fondamental dans le choix des décideurs.&lt;/p&gt;
&lt;p&gt;Il y a de cela quelques temps on pouvait sans risque conseiller de développer
une application uniquement pour iOS, dû à sa part de marché largement plus
importante.&lt;/p&gt;
&lt;p&gt;Ce n'est plus le cas aujourd'hui. &lt;a href="http://techland.time.com/2013/04/16/ios-vs-android/"&gt;Android est maintenant en tête
sur tous les segments mobile&lt;/a&gt;,
sans pour autant qu'iOS soit marginal.&lt;/p&gt;
&lt;p&gt;Cela signifie donc que, sauf usage interne, prototype ou marché extrêmement ciblé,
toute application devra être développée pour les deux plateformes.&lt;/p&gt;
&lt;p&gt;Il faudra également se poser la question des tablettes, et de la gestion
des différents terminaux sur ces OS mobiles.&lt;/p&gt;
&lt;p&gt;Les développements natifs seront indéniablement plus coûteux s'il faut développer
pour plusieurs plateformes. Les points suivants, comme la maintenance, sont également à prendre
en compte.&lt;/p&gt;
&lt;h3&gt;Maintenance&lt;/h3&gt;
&lt;p&gt;Le web excelle par sa capacité à mettre à disposition une nouvelle version d'une application instantanément,
et pour tout le monde.&lt;/p&gt;
&lt;p&gt;Nul besoin de maintenir X version pour Y plateforme ni de demander
la validation d'un store.&lt;/p&gt;
&lt;h3&gt;App store : avantage ou inconvénient ?&lt;/h3&gt;
&lt;p&gt;Pour les applications mobiles natives, l'app store est à double tranchant.&lt;/p&gt;
&lt;p&gt;En faire la une peut être une formidable déclencheur de succès, tout comme
son mauvais référencement peut considérablement vous pénaliser.&lt;/p&gt;
&lt;p&gt;Naviguer dans un store sur mobile n'est pas agréable. Toute comme les internautes
ne dépassent pas la première page de Roogle, ils ne parcourront pas 50 applications.&lt;/p&gt;
&lt;p&gt;Qui plus est l'accès y est payant et chaque nouvelle version doit faire l'objet
d'un validation.&lt;/p&gt;
&lt;p&gt;Toutefois les stores vous permettent de faire complètement abstraction
de la distribution de votre application (recherche, achat, mise à disposition),
ce qui est un point non négligeable.&lt;/p&gt;
&lt;h3&gt;Compétences&lt;/h3&gt;
&lt;p&gt;Un autre point est celui des compétences. Les compétences en développement
d'applications natives sont moins répandues que les compétences en développement web.&lt;/p&gt;
&lt;p&gt;Évidemment ce qui est plus rare est généralement plus cher.&lt;/p&gt;
&lt;h3&gt;Des développements natifs facilités&lt;/h3&gt;
&lt;p&gt;Le développement d'application mobiles natives s'est tout de même considérablement
amélioré avec des initiatives tierces comme &lt;a href="http://www.synbioz.com/blog/introduction_a_rubymotion"&gt;RubyMotion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;RubyMotion est au mobile ce que Ruby on Rails est au web, un formidable jeu
d'outils basés sur Ruby pour accélérer les développements d'applications mobiles sous iOS.&lt;/p&gt;
&lt;p&gt;Il ne pallie toutefois pas (encore ?) aux contraintes de publication et de
gestion des différentes plateformes telles qu'Android.&lt;/p&gt;
&lt;h3&gt;Un mot sur les solutions hybrides&lt;/h3&gt;
&lt;p&gt;Disons le clairement, les solutions type Titanium ou PhoneGap sont loin
de tenir leurs promesses.&lt;/p&gt;
&lt;p&gt;De manière générale le « Write once, run anywhere » (écris le une fois, fais le tourner partout)
a toujours été un mirage en informatique.&lt;/p&gt;
&lt;p&gt;Elles combinent de nombreux problèmes : lenteur, difficulté de débogage…&lt;/p&gt;
&lt;h3&gt;Quels arguments pour le natif ?&lt;/h3&gt;
&lt;p&gt;Les applications mobiles natives ont encore de solides arguments, tout
dépend du besoin.&lt;/p&gt;
&lt;p&gt;Dans la gestion du mode déconnecté, de la fluidité, des applications gourmandes en
performances ou en rendu 3D, elles restent devant.&lt;/p&gt;
&lt;p&gt;Les applications natives sont également appréciées pour leurs interfaces
utilisateurs cohérentes. Étant donné qu'elles doivent suivre les règles
de l'OS mobile, elles sont plus cohérentes.&lt;/p&gt;
&lt;p&gt;Elles conservent également l'immense avantage des habitudes utilisateurs.&lt;/p&gt;
&lt;p&gt;Très peu d'utilisateurs de smartphone savent par exemple comment créer
un raccourci vers une application web pour pouvoir la lancer comme une application mobile.&lt;/p&gt;
&lt;p&gt;À l'inverse, les stores sont maintenant très bien compris, en témoigne
l'App Store qui a fêté &lt;a href="http://www.apple.com/fr/itunes/50-billion-app-countdown/"&gt;son 50&lt;sup&gt;ème&lt;/sup&gt; milliard d'apps téléchargées&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Les applications web mobiles offrent des possibilités grandissantes pour des
coûts globalement moindres.&lt;/p&gt;
&lt;p&gt;Qui plus est, elles rattrapent leurs retards dans les domaines de prédilection
des apps mobiles natives (jeu 3D avec WebGL…).&lt;/p&gt;
&lt;p&gt;Enfin, Firefox OS, bien que partant avec beaucoup de retard,
est à suivre avec attention.&lt;/p&gt;
&lt;p&gt;Si vous devez construire une application mobile pour le moyen terme
l'option web est à considérer très attentivement, car elle ne cesse
de progresser.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Vos scripts ruby avec ARGF</title>
    <id>https://martin.catty.fr/blog/vos-scripts-ruby-avec-argf/</id>
    <link href="https://martin.catty.fr/blog/vos-scripts-ruby-avec-argf/" rel="alternate"/>
    <updated>2013-03-26T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Remplacer le classique ARGV&lt;/h3&gt;
&lt;p&gt;À l'écriture d'un script ruby on a tendance à utiliser ARGV,
pour lire les paramètres entrants.&lt;/p&gt;
&lt;p&gt;Généralement cela représente le chemin d'un fichier.&lt;/p&gt;
&lt;p&gt;Admettons que je veuille un script tout simple qui rajoute
le numéro de chaque ligne:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ARGV&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each_line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lineno&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Un appel type &lt;code&gt;./pipe.rb datas.csv&lt;/code&gt; fonctionne très bien mais comment faire en sorte que mon
script soit réutilisable et que je puisse lui passer du contenu en entrée type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo,bar\nbaz,coin"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./pipe.rb
./pipe.rb:4:in&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;initialize&lt;span class="s1"&gt;': can'&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;convert&lt;span class="w"&gt; &lt;/span&gt;nil&lt;span class="w"&gt; &lt;/span&gt;into&lt;span class="w"&gt; &lt;/span&gt;String&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;TypeError&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nous avons besoin que notre script suive les principes
UNIX et puisse être utilisé avec des pipes, en sortie ou en entrée.&lt;/p&gt;
&lt;p&gt;Pour cela nous avons à disposition &lt;a href="http://ruby-doc.org/core-2.0/ARGF.html"&gt;ARGF&lt;/a&gt;. ARGF se comporte comme un flux
qui est capable d'utiliser l'entrée standard (STDIN) ou un fichier.
Notre script va donc se transformer en:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ARGF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gets&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each_line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ARGF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lineno&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;La sortie (STDOUT) de echo va directement venir alimenter l'entrée de notre
script ruby (STDIN) avec le résultat suivant:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo,bar\nbaz,coin"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./pipe.rb
&lt;span class="m"&gt;1&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;foo,bar
&lt;span class="m"&gt;2&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;baz,coin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;À noter que cela fonctionne également avec un tail -f, exemple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;feed.csv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./pipe.rb
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo,bar\nbaz,coin"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;feed.csv
&lt;span class="m"&gt;1&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;foo,bar
&lt;span class="m"&gt;2&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;baz,coin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;De la même façon la sortie de notre script peut être réutilisée
dans l'entrée d'un utilitaire GNU.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat&lt;span class="w"&gt; &lt;/span&gt;datas.csv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./pipe.rb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Synbioz'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;results
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;J'espère que ces quelques lignes de Ruby (5!) vous permettrons d'agrémenter
vos scripts.&lt;/p&gt;
&lt;p&gt;Si vous avez d'autres astuces / bonnes pratiques à partager,
n'hésitez pas.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Ruby 2</title>
    <id>https://martin.catty.fr/blog/ruby-2/</id>
    <link href="https://martin.catty.fr/blog/ruby-2/" rel="alternate"/>
    <updated>2013-03-05T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Pour ses 20 ans, Ruby se voit offrir une release majeure, la 2.0.&lt;/p&gt;
&lt;p&gt;Notre langage préféré n'a pas pris une ride, au contraire il gagne en maturité.&lt;/p&gt;
&lt;h3&gt;Rétro-compatible&lt;/h3&gt;
&lt;p&gt;Le point le plus important au delà des évolutions du langage est que Ruby 2
est compatible avec Ruby 1.9.&lt;/p&gt;
&lt;p&gt;Cela va grandement faciliter le passage de l'un à l'autre. &lt;a href="http://www.synbioz.com/blog/rails_4"&gt;Rails 4&lt;/a&gt; est d'ailleurs
d'ores et déjà compatible avec Ruby 2.&lt;/p&gt;
&lt;h3&gt;Ruby continue sa croissance&lt;/h3&gt;
&lt;p&gt;En comparaison des autres langages, le nombre de gems disponibles et la croissance
associée sont assez impressionnants.&lt;/p&gt;
&lt;p&gt;Seul node semble être plus dynamique actuellement.&lt;/p&gt;
&lt;p&gt;Attention le nombre de modules disponibles ne présume en rien
de la qualité intrinsèque du langage.&lt;/p&gt;
&lt;p&gt;Mais cela demeure quand même le gage, pour ceux qui en douterait encore,
que Ruby n'est pas une voie de garage.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/modules.png"&gt;&lt;img alt="Modules disponibles" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/modules_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Par contre l'encéphalogramme de PHP semble vraiment très plat &amp;lt;/troll&amp;gt;.&lt;/p&gt;
&lt;h3&gt;Nouveautés de Ruby 2&lt;/h3&gt;
&lt;h4&gt;Arguments nommés&lt;/h4&gt;
&lt;p&gt;Il est rare en Ruby d'avoir des méthodes avec un grand nombre d'arguments, et ceci
par soucis de lisibilité et de cohérence (inutile de se soucier de l'ordre des paramètres).&lt;/p&gt;
&lt;p&gt;Dans le cas où un grand nombre de valeurs doivent être passées on utilise un Hash.&lt;/p&gt;
&lt;p&gt;Rails en fait d'ailleurs grand usage par le biais de sa méthode &lt;code&gt;extract_options!&lt;/code&gt;,
qui permet d'extraire les hash.&lt;/p&gt;
&lt;p&gt;L'inconvénient est que la définition de la méthode offre peu de visibilité sur
ce qu'elle va avoir à traiter.&lt;/p&gt;
&lt;p&gt;Les paramètres nommés existent déjà en Objective-C, et par conséquent cela a été repris
dans l'implémentation de &lt;a href="http://www.Rubymotion.com/"&gt;RubyMotion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Cette fonctionnalité existe maintenant dans Ruby 2.&lt;/p&gt;
&lt;p&gt;On peut donc définir des méthodes de la sorte:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;L'inconvénient est qu'une valeur par défaut doit obligatoirement être
fournie pour les paramètres nommés. Cela ne devrait toutefois plus être
le cas dans les prochaines versions de Ruby.&lt;/p&gt;
&lt;p&gt;De même si on passe un argument qui n'est pas inclus dans la définition
on aura un &lt;code&gt;ArgumentError&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ArgumentError: unknown keyword: debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Il existe toutefois un moyen de palier à cela en utilisant la notation &lt;code&gt;**&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;À noter également qu'il est possible de faire des choses très avancées dans les
paramètres, à savoir définir un paramètre qui utilise la valeur d'un autre ou encore
appeler une méthode pour initialiser la valeur par défaut d'un paramètre.&lt;/p&gt;
&lt;p&gt;Ex:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;defaults&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0.0"&lt;/span&gt;

&lt;span class="n"&gt;a1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Création de symboles&lt;/h3&gt;
&lt;p&gt;Vous avez sûrement l'habitude de créer des tableaux de mots avec %w:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sx"&gt;%w(foo bar baz)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"baz"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;On peut désormais faire la même chose avec des symboles avec &lt;code&gt;%i&lt;/code&gt; ou &lt;code&gt;%I&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:baz&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;UTF-8, de base&lt;/h3&gt;
&lt;p&gt;UTF-8 est maintenant utilisé de base, il n'y a plus lieu de le spécifier
en haut de ses fichiers dès lors que l'on utilise quelques caractères exotiques (latin, unicode…).&lt;/p&gt;
&lt;h3&gt;Warning sur les variables inutilisées&lt;/h3&gt;
&lt;p&gt;Depuis 1.9 l'interpréteur offre des warning sur les variables non utilisées.&lt;/p&gt;
&lt;p&gt;Généralement le moyen de l'éviter est d'utiliser &lt;code&gt;_&lt;/code&gt;.
Par exemple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Maintenant il est aussi possible de préfixer n'importe quelle variable avec &lt;code&gt;_&lt;/code&gt;
pour éviter les warning.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Prepend sur les modules&lt;/h3&gt;
&lt;p&gt;Si vous utilisez rails vous connaissez sûrement &lt;code&gt;prepend_before_filter&lt;/code&gt;.
Le prepend va s'assurer d'ajouter le filtre au début de la chaîne d'éxécution.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;before_filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:a&lt;/span&gt;
&lt;span class="n"&gt;prepend_before_filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:b&lt;/span&gt;
&lt;span class="c1"&gt;# b sera appelé avant a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Le prepend introduit dans Ruby 2 a la même vocation. Il va vous permettre de vous
assurer que votre inclusion a lieu au début de la chaîne d'éxécution.&lt;/p&gt;
&lt;p&gt;En toute logique vous disposez d'un callback dans le module lorsqu'il est &lt;code&gt;prepend&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Bar&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepended&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Prepended."&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;included&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Included."&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Foo&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;prepend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Bar&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ancestors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Baz&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Bar&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Baz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ancestors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Prepended.&lt;/span&gt;
&lt;span class="c1"&gt;# Bar, Foo, Object, Kernel, BasicObject&lt;/span&gt;
&lt;span class="c1"&gt;# Included.&lt;/span&gt;
&lt;span class="c1"&gt;# Baz, Bar, Object, Kernel, BasicObject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Refinements&lt;/h3&gt;
&lt;p&gt;Les refinements sont selon moi une vraie killer feature.&lt;/p&gt;
&lt;p&gt;La grande promesse des refinements est de limiter voire éviter le monkey patching.&lt;/p&gt;
&lt;p&gt;Lorsqu'on commence à goûter à la puissance de Ruby on est très friand de ré-ouvrir
des classes au runtime et d'ajouter des méthodes à la volée dans des core classes.&lt;/p&gt;
&lt;p&gt;Avec l'expérience c'est une tâche que l'on essaie d'éviter au maximum car cela
n'offre aucune garantie.&lt;/p&gt;
&lt;p&gt;N'importe qui peut avoir fait la même chose dans une lib,
c'est à dire (re-)définir une méthode avec le même nom et écraser la votre.&lt;/p&gt;
&lt;p&gt;L'objectif des refinements est de pouvoir limiter la portée d'un ajout et surtout l'obliger
de manière explicite ; là ou une inclusion classique aurait une portée globale.
Toutefois nous allons voir que cet objectif n'est pas réellement atteint.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ExtendString&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;refine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;words&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[[:alnum:]]+/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Le petit chaperon rouge."&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# undefined method `words' for "Le petit chaperon rouge.":String (NoMethodError)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ExtendString&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;refine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;words&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[[:alnum:]]+/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ExtendString&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Le petit chaperon rouge."&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Contrairement à ce qu'on peut parfois lire,
using n'est pas utilisable à l'heure actuelle dans le contexte d'une classe ou d'un module (&lt;code&gt;undefined method using&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Selon moi cela limite beaucoup son intérêt, puisqu'on ne pourra plus limiter finement sa portée.
On n'aura que 2 états, avant et après using.&lt;/p&gt;
&lt;p&gt;Toutefois, il faut intégrer que le statut des refinements est expérimental et que l'API peut changer.&lt;/p&gt;
&lt;p&gt;Son usage est d'ailleurs actuellement déconseillé. Vous aurez un «warning: Refinements are experimental, and the behavior may change in future versions of Ruby!»,
sans même utiliser l'interpréteur avec -w.&lt;/p&gt;
&lt;h3&gt;Répertoire courant&lt;/h3&gt;
&lt;p&gt;Si vous trouviez aussi que &lt;code&gt;File.dirname(__FILE__)&lt;/code&gt; n'était pas spécialement élégant,
cela tombe bien on a maintenant &lt;code&gt;__dir__&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Par contre je trouve dommage d'avoir une casse différente pour les deux.&lt;/p&gt;
&lt;h3&gt;Recherche binaire&lt;/h3&gt;
&lt;p&gt;Ruby possède maintenant une méthode pour rechercher une valeur dans un tableau trié.&lt;/p&gt;
&lt;p&gt;Il s'agit d'une simple méthode dichotomique qui va donc couper le tableau en deux et
comparer la valeur avec la valeur du milieu du tableau.&lt;/p&gt;
&lt;p&gt;Selon le résultat, si non trouvé, la recherche se poursuit dans la moitié inférieure ou
supérieure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bsearch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;387&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 388&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Transformer une structure en hash&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bar'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_h&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {:foo=&amp;gt;"bar"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;À noter que ce n'est pas disponible pour toutes les classes, ce qui semble logique,
étant donné que Ruby ne peut pas deviner comment vous voudriez transformer un Array en Hash
par exemple.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;to_h&lt;/code&gt; est disponible pour &lt;code&gt;Struct&lt;/code&gt;, &lt;code&gt;OpenStruct&lt;/code&gt; et &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Énumérateurs lazy&lt;/h3&gt;
&lt;p&gt;L'intérêt d'un énumérateur lazy c'est que l'ensemble de la chaîne de méthode sera interprétée
à chaque tour de boucle:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="no"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;INFINITY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lazy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;even?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;C'est très pratique pour d'énormes volumes de données car cela évite de se retrouver
avec des tableaux intermédiaires.&lt;/p&gt;
&lt;p&gt;Surtout dans ce cas présent où la borne supérieure est infinie donc la création du tableau l'est également
et que par conséquent l'interpréteur planterait sans lazy.&lt;/p&gt;
&lt;p&gt;À noter toutefois qu'il ne convient pas d'utiliser lazy à toutes les sauces, sur des tableaux de données
réduits &lt;code&gt;lazy&lt;/code&gt; sera beaucoup plus lent que l'implémentation sans.&lt;/p&gt;
&lt;h3&gt;Méthode appelante&lt;/h3&gt;
&lt;p&gt;Dans le cas d'un debug il est souvent pratique d'avoir un aperçu de la chaine d'éxécution.&lt;/p&gt;
&lt;p&gt;Retrouver la méthode appelante est très pratique pour cela. Précédemment en Ruby ce n'était pas très fiable
et particulièrement lent.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;caller_locations&lt;/code&gt; vous permet d'obtenir le nom du fichier, la ligne et le contexte appelant.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;A&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;hi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;caller_locations&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hi called by &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;caller_locations&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;hello&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello called by &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;caller_locations&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hi&lt;/span&gt;
&lt;span class="c1"&gt;# test.rb:70:in `&amp;lt;main&amp;gt;'&lt;/span&gt;
&lt;span class="c1"&gt;# Hi called by &amp;lt;main&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# Hello called by hi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Performance&lt;/h3&gt;
&lt;p&gt;Je n'ai pas encore eu l'occasion de benchmarker directement Ruby 2.0 en comparaison avec 1.9.&lt;/p&gt;
&lt;p&gt;De profonds changements sont annoncés au niveau du garbage collector, du mécanisme de fork mais
je préfère tester avant de vous en parler.&lt;/p&gt;
&lt;h3&gt;Comment l'installer ?&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/sstephenson/Ruby-build"&gt;Ruby-build&lt;/a&gt; possède une formule prête à l'emploi.&lt;/p&gt;
&lt;p&gt;J'ai vu passer quelques messages de personnes ayant eu des soucis avec openssl, mais je dois dire que je n'en ai rencontré aucun
sur les différentes machines ou je l'ai installé (uniquement Mac).&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Cette version s'annonce comme un très bon cru, qui plus est «quasiment» utilisable en l'état.
Alors n'attendez plus, à vos claviers !&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>L'authenticity token dans ruby on rails</title>
    <id>https://martin.catty.fr/blog/lauthenticity-token-dans-ruby-on-rails/</id>
    <link href="https://martin.catty.fr/blog/lauthenticity-token-dans-ruby-on-rails/" rel="alternate"/>
    <updated>2013-02-07T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Ruby on Rails nous offre tellement de mécanismes d'abstraction qu'on en oublie parfois les bases.&lt;/p&gt;
&lt;p&gt;Dans l'idée de notre article sur le &lt;a href="http://www.synbioz.com/blog/comment_fonctionne_les_sessions"&gt;fonctionnement des sessions&lt;/a&gt;,
découvrons aujourd'hui pourquoi et comment fonctionne l'authenticity token.&lt;/p&gt;
&lt;h3&gt;Pourquoi un mécanisme de token ?&lt;/h3&gt;
&lt;p&gt;L'objectif est de prévenir les attaques de type &lt;a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery"&gt;Cross site request forgery&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le fonctionnement est simple, vous vous authentifiez sur votre application A.&lt;/p&gt;
&lt;p&gt;Sur une autre application B vous utilisez un formulaire ou un lien qui
pointe en fait vers une action de l'application A sans que vous le sachiez.&lt;/p&gt;
&lt;p&gt;En l'absence de mécanisme de contrôle vous pouvez modifier voire supprimer des resources
sur l'application A sans même vous en rendre compte.&lt;/p&gt;
&lt;p&gt;En tant qu'utilisateur il y a un premier de niveau de sécurité à apporter ;
il s'agit d'éviter de rester connecter sur des services qu'on n'est pas entrain
d'utiliser.&lt;/p&gt;
&lt;p&gt;En somme dès que j'arrête d'utiliser un service je me déconnecte.&lt;/p&gt;
&lt;p&gt;Côté développeur il faut absolument éviter de proposer des actions
suceptibles de modifier des ressources sous forme de lien ou de form en GET,
car dans ce cas les token ne vous protégeront pas comme nous le verrons ci dessous.&lt;/p&gt;
&lt;p&gt;Au delà du GET on a déjà un premier volet de sécurité concernant la politique
de sécurité des navigateurs pour les appels en AJAX depuis un autre domaine.&lt;/p&gt;
&lt;p&gt;Voir à ce sujet les commentaires de l'article sur &lt;a href="http://www.synbioz.com/blog/rails_4"&gt;rails 4&lt;/a&gt; à propos de CORS.&lt;/p&gt;
&lt;h3&gt;Comment le token peut parer à ça ?&lt;/h3&gt;
&lt;p&gt;Quand une vue avec un formulaire est rendue, Rails y ajoute un token
aléatoire dans un champ caché, et dans la session de l'utilisateur.&lt;/p&gt;
&lt;p&gt;À la soumission du formulaire, un contrôle sera fait pour s'assurer
que le token est soumis et que sa valeur correspond à celle de la session.&lt;/p&gt;
&lt;h3&gt;Concrètement ça fonctionne comment ?&lt;/h3&gt;
&lt;p&gt;Le code de génération du token est très simple, il se trouve dans ActionPack:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Sets the token value for the current session.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;form_authenticity_token&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:_csrf_token&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;La vérification de la requête est elle aussi basique, Rails va juste vérifier
qu'il ne s'agit pas d'une requête GET, HEAD, que la protection est activée
et que le token est présent dans les params ou dans les headers, dans le cas
d'une requête venant d'un autre domaine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;verified_request?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;protect_against_forgery?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;form_authenticity_token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;request_forgery_protection_token&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;form_authenticity_token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'X-CSRF-Token'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Voilà tout pour cet article sur le fonctionnement des tokens.&lt;/p&gt;
&lt;p&gt;On trouve trop souvent des articles ou des fils de discussion qui recommandent
de désactiver la protection au moindre souci (souvent dans le cas d'utilisation conjointe avec AJAX).&lt;/p&gt;
&lt;p&gt;Ne faites pas ça, creusez vous la tête et fixez le problème plutôt
que d'ouvrir des brèches de sécurité.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Rails 4</title>
    <id>https://martin.catty.fr/blog/rails-4/</id>
    <link href="https://martin.catty.fr/blog/rails-4/" rel="alternate"/>
    <updated>2013-01-08T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Nouveautés de Rails 4&lt;/h3&gt;
&lt;h4&gt;Strong parameters&lt;/h4&gt;
&lt;p&gt;L'idée est de déporter la gestion des paramètres protégés du modèle
vers le contrôleur.&lt;/p&gt;
&lt;p&gt;Cela a du sens dans la mesure où l'autorisation ou non d'écrire
un champ est très largement liée au contexte d'exécution (qui suis je, où…).&lt;/p&gt;
&lt;p&gt;L'inconvénient est que cela risque de nous amener à écrire plus de code,
ce à quoi nous ne sommes pas vraiment habitués.&lt;/p&gt;
&lt;p&gt;La suppression des attr_accessible ou attr_protected risque d'être un peu
déroutante, dans la mesure ou nous n'aurons pas de protection «par défaut»
sur les modèles.&lt;/p&gt;
&lt;p&gt;Un exemple de ce qu'il sera possible de faire:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ForbiddenAttributesProtection&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;UsersController&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_parameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;private&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;user_parameters&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin?&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:firstname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:lastname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:firstname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:lastname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;L'inclusion de &lt;code&gt;ActiveModel::ForbiddenAttributesProtection&lt;/code&gt; permet
de garantir que du code tel que:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;ne fonctionnera plus. Sans cela les deux versions fonctionneraient.&lt;/p&gt;
&lt;p&gt;À noter que &lt;code&gt;strong_parameters&lt;/code&gt; est utilisable en tant que Gem dans
des projets rails 3.&lt;/p&gt;
&lt;h4&gt;Turbolinks&lt;/h4&gt;
&lt;p&gt;Le principe est d'éviter de recharger l'intégralité d'une page
lorsque l'on suit un lien.&lt;/p&gt;
&lt;p&gt;Le mécanisme de base est le même que pjax, c'est à dire AJAX + &lt;a href="/blog/2011/08/16/introduction_a_html5"&gt;pushState
prévu dans HTML5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Cela signifie que l'on exécute des requêtes asynchrones mais que l'historique
est mis à jour ce qui autorise l'usage des boutons du navigateur tel que
le retour à la page précédente.&lt;/p&gt;
&lt;p&gt;Le postulat de départ est que les ressources type javascript ou CSS
ne changent pas d'une page à l'autre et que l'on peut donc éviter d'avoir
à les recharger.&lt;/p&gt;
&lt;p&gt;Pour cela Turbolinks va donc aller récupérer le contenu de la page,
mettre à jour les meta et autre title et changer uniquement le body.&lt;/p&gt;
&lt;p&gt;De même si vous enregistrez vos événements manuellement après que le DOM soit prêt cela ne fonctionnera pas, car l'évènement dom:loaded n'est plus envoyé.&lt;/p&gt;
&lt;p&gt;Turbolinks offre un mécanisme de substitution en envoyant ses propres évènements,
tel que &lt;code&gt;page:fetch&lt;/code&gt; (pour lancer le chargement), &lt;code&gt;page:load&lt;/code&gt; (la page a été renvoyée par le serveur),
&lt;code&gt;page:restore&lt;/code&gt; (la page a été rechargée depuis le cache), &lt;code&gt;page:change&lt;/code&gt; (la page a été remplacée par
la nouvelle version).&lt;/p&gt;
&lt;p&gt;Comme pour une Single Page Application, il faudra également faire attention à éviter les fuites mémoires.&lt;/p&gt;
&lt;p&gt;L'avantage de Turbolinks est de se dégrader correctement, en effet l'application
fonctionnera tout aussi bien sans, cela n'entrave pas la navigation.&lt;/p&gt;
&lt;p&gt;Ce qui signifie qu'il n'y a pas de problème potentiel non plus pour votre référencement.&lt;/p&gt;
&lt;h4&gt;Stream&lt;/h4&gt;
&lt;p&gt;Un contrôleur peut maintenant inclure &lt;code&gt;ActionController::Live&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cela lui permet d'émettre une réponse sous forme d'un stream, plutôt
que d'un fichier complet à interpréter ou télécharger.&lt;/p&gt;
&lt;p&gt;Les méthodes utilisées sont &lt;code&gt;response.stream.write&lt;/code&gt; et &lt;code&gt;response.stream.close&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;L'avantage est de pouvoir envoyer une réponse morcelée et potentiellement
interprétable plus tôt par le navigateur.&lt;/p&gt;
&lt;p&gt;Pour exploiter au mieux le potentiel de streaming il vaut mieux utiliser un serveur
adapté comme &lt;a href="https://github.com/puma/puma"&gt;puma&lt;/a&gt;, &lt;a href="http://rainbows.rubyforge.org/"&gt;rainbows&lt;/a&gt; ou &lt;a href="http://code.macournoyer.com/thin/"&gt;thin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le souci avec Unicorn est qu'il ferme la connexion après 30 secondes, son objectif
étant de livrer la réponse le plus vite possible, ce qui n'est pas adapté pour du stream.&lt;/p&gt;
&lt;h4&gt;Verbe HTTP PATCH&lt;/h4&gt;
&lt;p&gt;Dans la logique REST, PUT concerne une modification sur l'intégralité de
l'objet. C'est à dire que le formulaire devrait soumettre tous les attributs.&lt;/p&gt;
&lt;p&gt;Une modification partielle d'objet doit normalement être effectué avec PATCH.&lt;/p&gt;
&lt;p&gt;Comme les navigateurs ne supportent pas plus nativement PATCH que PUT et DELETE,
la méthode traditionnelle sera utilisée, à savoir &lt;code&gt;_method=patch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;PATCH devriendra l'usage par défaut pour les objets en modification mais PUT
continuera de fonctionner.&lt;/p&gt;
&lt;h4&gt;Relations&lt;/h4&gt;
&lt;h4&gt;All&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Model.all&lt;/code&gt; sera désormais considéré comme un scope et ne retournera plus les enregistrements.
C'est tout à fait logique et c'est déjà comme ça que c'est fait avec mongoid,
qui retourne &lt;code&gt;Mongoid::Criteria&lt;/code&gt;, comme pour un scope.&lt;/p&gt;
&lt;p&gt;Pour ActiveRecord, c'est &lt;code&gt;ActiveRecord::Relation&lt;/code&gt; qui sera retourné.
Pour avoir directement les entités un &lt;code&gt;Model.to_a&lt;/code&gt; fera l'affaire.&lt;/p&gt;
&lt;h4&gt;None&lt;/h4&gt;
&lt;p&gt;None est un scope à part entière qui ne retourne aucun résultat. Je n'ai
jamais eu de cas ou je souhaitais utiliser des scope qui ne retourne rien,
donc j'ai du mal à en voir l'intérêt.&lt;/p&gt;
&lt;p&gt;Si vous avez des cas d'usages, dites le moi en commentaire.&lt;/p&gt;
&lt;h4&gt;First, last&lt;/h4&gt;
&lt;p&gt;L'appel à &lt;code&gt;first&lt;/code&gt; et &lt;code&gt;last&lt;/code&gt; fera explicitement un tri sur id, plutôt
que de laisser le choix à votre SGBD.&lt;/p&gt;
&lt;p&gt;À noter que si un default_scope est utilisé c'est bien lui qui sera pris en compte.&lt;/p&gt;
&lt;h4&gt;Asset pipeline&lt;/h4&gt;
&lt;p&gt;Le pipeline a été &lt;a href="https://github.com/rails/sprockets-rails"&gt;déporté du cœur de Rails&lt;/a&gt;, pour évoluer de façon autonome.&lt;/p&gt;
&lt;p&gt;sprocket-rails évolue donc à un rythme plus soutenu, en calant sa fréquence sur celle
de sprocket et en adoptant le même versionning.&lt;/p&gt;
&lt;p&gt;Étant disponible en tant que gem, le réglage &lt;code&gt;config.assets.enabled&lt;/code&gt; a été retiré.
Pour ne pas utiliser le pipeline il suffit de ne pas utiliser la gem.&lt;/p&gt;
&lt;p&gt;Les dossiers de cache ont été séparés par environnement, ce qui va nous simplifier
la vie concernant les tests d'intégration.&lt;/p&gt;
&lt;p&gt;La précompilation a été revue et très largement améliorée en terme de temps de traitement.
Sur les premières démo on parle d'un facteur 5. À confirmer à l'usage.&lt;/p&gt;
&lt;p&gt;À noter que la version 3 de sprocket-rails devrait voir l'arrivée de source maps, qui permet de faire concorder
un asset original avec sa version minifiée, ce qui offre le meilleur des 2 mondes: un
code maintenable, débugable et dont l'accès à la doc est simplifié tout en ayant la performance
apportée par la minification.&lt;/p&gt;
&lt;p&gt;Cette version devrait concorder avec la sortie de Rails 4.1.&lt;/p&gt;
&lt;h4&gt;References&lt;/h4&gt;
&lt;p&gt;On l'a vu à plusieurs reprises la tendance générale est à l'explicite dans Ruby on Rails.
Le principe est conservé pour &lt;code&gt;references&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;En rails 3 &lt;code&gt;includes&lt;/code&gt; est utilisé pour éviter les requêtes n+1.
Cela a pour effet de générer 2 requêtes SQL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"posts"&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"comments"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;"post_id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;En rails 4 cela ne fonctionnera pas si vous précisez des
conditions de requêtes via une string, car Rails ne sait pas quel objet
vous décrivez dans ce cas.&lt;/p&gt;
&lt;p&gt;Par exemple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"comments.created_at &amp;gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yesterday&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;ne fonctionnera pas car une jointure n'est pas faite automatiquement avec comments.
Vous devrez le spécifier via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"comments.created_at &amp;gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yesterday&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;À ce moment ce ne sont plus 2 requêtes qui seront exécutées mais 1 seule avec un OUTER JOIN.&lt;/p&gt;
&lt;p&gt;Par contre si vous utilisez des requêtes simples ou des symboles, Rails saura se débrouiller seul.&lt;/p&gt;
&lt;p&gt;Ex:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"comments.created_at"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4&gt;Évaluation des scopes&lt;/h4&gt;
&lt;p&gt;En rails 3, tout code dynamique entrainait le besoin d'encadrer
son scope dans une lambda, afin d'éviter que l'interprétation se fasse
au runtime.&lt;/p&gt;
&lt;p&gt;Désormais pour éviter toute confusion, tout scope devra être enrobé,
même s'il n'y a pas de risque de confusion comme par exemple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;deviendra:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4&gt;Mise à jour d'attributs&lt;/h4&gt;
&lt;p&gt;À l'image de &lt;code&gt;update_attributes&lt;/code&gt;, &lt;code&gt;update_columns&lt;/code&gt; fait
son apparition et sera le pendant de &lt;code&gt;update_column&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Voici un petit mémo sur qui fait quoi:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mise à jour d'attributs" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/update_thumb_450.png" /&gt;&lt;/p&gt;
&lt;p&gt;Le update_attribute est souvent utilisé sans savoir qu'il ne joue pas les validations.&lt;/p&gt;
&lt;p&gt;De même les update_attribute(s) mettent à jour les dirty attributes, c'est à dire ceux dont
la valeur a changé entre temps.&lt;/p&gt;
&lt;p&gt;Ex:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firstname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;
&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_attributes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;lastname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Doe"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;entraine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"users"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"firstname"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'John'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"lastname"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Doe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"updated_at"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'2013-01-08 09:09:21.050217'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;"id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;La recommandation est de ne plus utiliser &lt;code&gt;update_attribute&lt;/code&gt;, qui est en &lt;a href="https://github.com/rails/rails/commit/63bcfbb2386ac974a0087476a0c58634963a0be9"&gt;passe d'être
déprécié&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;C'est une situation assez compliquée. Logiquement si on fait le choix d'avoir
&lt;code&gt;update_column&lt;/code&gt; et &lt;code&gt;update_columns&lt;/code&gt;, il n'y a pas de raison de retirer &lt;code&gt;update_attribute&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour beaucoup son retrait aurait entrainé du code cassé lors de la mise à jour.&lt;/p&gt;
&lt;p&gt;En le gardant et en faisant en sorte que son comportement soit le même que &lt;code&gt;update_attributes&lt;/code&gt;,
on risque aussi de casser du code mais de façon beaucoup plus critique car silencieuse.&lt;/p&gt;
&lt;p&gt;Pourtant je suis surpris que le choix n'est pas été fait d'homogénéiser l'API. Le passage
à une nouvelle version majeure aurait été une bonne occasion.&lt;/p&gt;
&lt;p&gt;En résumé on vous conseille de ne plus l'utiliser mais rien ne change si vous choisissez
de le faire quand même.&lt;/p&gt;
&lt;h3&gt;Retraits de Ruby on Rails 4&lt;/h3&gt;
&lt;h4&gt;Plugins&lt;/h4&gt;
&lt;p&gt;Les plugins n'étaient plus guère utilisé en Rails 3. Ils laissent donc définitivement
leur place au profit des gems.&lt;/p&gt;
&lt;h4&gt;Finder&lt;/h4&gt;
&lt;p&gt;Le find ne disparait pas en tant que tel, mais plutôt l'utilisation de symboles associés, comme &lt;code&gt;find(:first)&lt;/code&gt;, &lt;code&gt;find(:last)&lt;/code&gt;, &lt;code&gt;find(:all)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Des méthodes étant prévues au niveau modèle, autant rester &lt;acronym title="Don't repeat yourself"&gt;DRY&lt;/acronym&gt;.&lt;/p&gt;
&lt;p&gt;Les finder dynamiques sont par contre retirés aux dépens du where.
C'est à dire:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;find_all_by&lt;/li&gt;
&lt;li&gt;find_last_by&lt;/li&gt;
&lt;li&gt;find_or_initialize_by&lt;/li&gt;
&lt;li&gt;find_or_create_by&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je ne suis par contre pas très fan de la méthode de substitution pour les
initialize, à savoir &lt;code&gt;where(…).first_or_initialize&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Object#id&lt;/h4&gt;
&lt;p&gt;Qui n'a jamais vu cette erreur ?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Celle ci vient du fait que 4 est l'object_id de nil. Rails vous prévient
que vous vous êtes peut être trompé en appelant id à la place de object_id.&lt;/p&gt;
&lt;p&gt;Désormais vous aurez toujours l'erreur mais elle sera affichée différemment,
avec un simple &lt;code&gt;NoMethodError: undefined method id for nil:NilClass&lt;/code&gt;, comme
pour n'importe quelle autre méthode.&lt;/p&gt;
&lt;p&gt;C'est logique car il est relativement rare d'avoir à utiliser les object_id dans son application.&lt;/p&gt;
&lt;h4&gt;Gestion du cache&lt;/h4&gt;
&lt;p&gt;Les mécanismes pour mettre en cache une page ou une action sont retirés.&lt;/p&gt;
&lt;p&gt;Seuls les caches de fragments sont maintenus. Ces caches seront
nommés grâce au hash de leur contenu, ce qui permettra de les faire expirer
automatiquement si leur contenu change (puisque leur nom changera également).&lt;/p&gt;
&lt;p&gt;Cela permet également de gérer des caches imbriqués de façon transparente.&lt;/p&gt;
&lt;p&gt;Fini donc le versionning de cache manuel et fastidieux, tel que:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% cache &lt;/span&gt;&lt;span class="s2"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="sx"&gt;&amp;lt;% end %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;La gestion se fait maintenant de manière beaucoup plus simple et automatisée:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% cache &lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="sx"&gt;&amp;lt;% end %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4&gt;Authenticity token&lt;/h4&gt;
&lt;p&gt;En rails 3, le token est automatiquement ajouté par Ruby on Rails dans les formulaires
ou les liens qui lient vers des contenu non GET.&lt;/p&gt;
&lt;p&gt;Si le token n'est pas présent &lt;code&gt;rails.js&lt;/code&gt; se chargeait de l'ajouter.&lt;/p&gt;
&lt;p&gt;Ce ne sera plus le cas pour les formulaire soumis en AJAX (&lt;code&gt;remote: true&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;En effet la police de sécurité fait qu'il n'est pas possible de soumettre un
formulaire en AJAX de depuis un autre domaine, ce qui permet d'éviter cette
vérification dans le cas de requêtes de ce type.&lt;/p&gt;
&lt;p&gt;Cela permet également de mettre en cache les formulaires. Il y a toutefois
un inconvénient qui n'est pas neutre, les formulaires ne fonctionneront
plus en l'état pour ceux n'ayant pas activé JavaScript.&lt;/p&gt;
&lt;p&gt;Pour forcer l'inclusion d'un token l'option &lt;code&gt;authenticity_token: true&lt;/code&gt; peut
être utilisée.&lt;/p&gt;
&lt;h3&gt;Non inclusion dans Rails 4&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Rails.queue&lt;/code&gt; était un peu la grosse nouveauté mise en avant pour Rails 4.
Elle a &lt;a href="https://github.com/rails/rails/commit/f9da785d0b1b22317cfca25c15fb555e9016accb"&gt;finalement été retirée&lt;/a&gt; et ne sera pas incluse dans cette release.&lt;/p&gt;
&lt;p&gt;Cela serait dû à des questions restées pour le moment sans réponse concernant
le design de l'API de queue.&lt;/p&gt;
&lt;p&gt;Le retrait est d'autant plus dommage qu'il se fait tardivement.
Toujours est il que la question reste ouverte, la volonté est toujours
affichée d'intégrer un mécanisme de queue dans Rails, mais pas sans une
API consistante.&lt;/p&gt;
&lt;h3&gt;Pré-requis de rails 4&lt;/h3&gt;
&lt;p&gt;Rails 4 nécessitera Ruby 1.9.3 pour fonctionner.&lt;/p&gt;
&lt;p&gt;En rails 4 la plupart des choses qui changent et vont être retirées sont juste
dépréciées. Elles seront effectivement retirées en 4.1.&lt;/p&gt;
&lt;h3&gt;Est ce que je peux migrer vers rails 4 ?&lt;/h3&gt;
&lt;p&gt;Sur une application récente et avec une bonne couverture de tests, la migration
ne devrait pas s'avérer trop complexe.&lt;/p&gt;
&lt;p&gt;Sur une application plus ancienne (type rails 2) je ne saurai trop vous conseiller de générer
un squelette d'application en rails 4 et d'y migrer votre code progressivement.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;À mon goût la sortie de Rails 4 est moins excitante que celle de Rails 2 et 3.&lt;/p&gt;
&lt;p&gt;La tête de gondole qu'était le système de queue a été retiré et la plupart des choses
présentées sont des consolidations. Ce qui est très bien, mais pas forcément très enthousiasmant.&lt;/p&gt;
&lt;p&gt;Par ailleurs la tendance est clairement à l'explicite. Quand il s'agit de sécurité
c'est bien sûr indispensable et je pense que la &lt;a href="http://www.h-online.com/security/news/item/GitHub-security-incident-highlights-Ruby-on-Rails-problem-1463207.html"&gt;vulnérabilité dévoilée sur Github&lt;/a&gt; aura
été un des facteurs déclenchant pour revoir le système de mise à jour des objets.&lt;/p&gt;
&lt;p&gt;En clair Ruby on Rails est entrain de devenir un framework très puissant, dont
la volonté est d'intégrer les dernières possibilités offertes par le web.&lt;/p&gt;
&lt;p&gt;Son histoire liée à celle de basecamp est un énorme avantage. La refonte de ce dernier,
basecamp next, aura permis d'apporter son lot de bonnes idées à Rails,
notamment en terme de performances.&lt;/p&gt;
&lt;p&gt;Je vous avez fait part de &lt;a href="http://www.synbioz.com/blog/2011/07/13/le_devenir_de_rails"&gt;mes inquiétudes à ce sujet&lt;/a&gt; et force est de constater que les choses vont
dans le bon sens.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Seetemap 1.2</title>
    <id>https://martin.catty.fr/blog/seetemap-1.2/</id>
    <link href="https://martin.catty.fr/blog/seetemap-1.2/" rel="alternate"/>
    <updated>2013-01-03T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;En cette fin d'année 2012 nous avons travaillé dur sur &lt;a href="https://seetemap.com"&gt;seetemap&lt;/a&gt; pour tenir
compte de vos remontées.&lt;/p&gt;
&lt;p&gt;Nous vous remercions car vous avez été nombreux à tester seetemap
et nous apporter vos remarques constructives.&lt;/p&gt;
&lt;p&gt;Nous les avons prises en compte pour cette nouvelle itération.&lt;/p&gt;
&lt;h3&gt;Lancement d'audit dès la page d'accueil&lt;/h3&gt;
&lt;p&gt;Nous le savons et vous l'avez confirmé, s'inscrire à un service
pour le tester est un frein pour beaucoup.&lt;/p&gt;
&lt;p&gt;Dans cette optique nous avons mis en place la possibilité de
lancer la création de sitemap dès votre arrivée sur le site.&lt;/p&gt;
&lt;p&gt;Nous vous demandons uniquement une URL et votre email pour vous
prévenir quand le sitemap est disponible.&lt;/p&gt;
&lt;h3&gt;Plans sur mesure&lt;/h3&gt;
&lt;p&gt;Nous ne pensions pas l'implémenter si rapidement mais pour certains
la limitation à 10 sites était rédhibitoire.&lt;/p&gt;
&lt;p&gt;C'est vrai qu'il aurait été dommage de créer plusieurs comptes pour cela.&lt;/p&gt;
&lt;p&gt;Vous pouvez désormais ajouter des packs, par tranche de 10 sites, et ce
jusque 100.&lt;/p&gt;
&lt;h3&gt;Filtrer les pages avec paramètres&lt;/h3&gt;
&lt;p&gt;Ce sont souvent celles que vous retirez en premier de vos sitemap,
car elles lient souvent un contenu déjà lié autrement.&lt;/p&gt;
&lt;p&gt;En deux clics vous pouvez donc les retirer durablement de votre sitemap.&lt;/p&gt;
&lt;p&gt;En effet, une règle est automatiquement créée et les nouvelles pages
contenant des paramètres seront automatiquement filtrées lors des prochains audits.&lt;/p&gt;
&lt;h3&gt;Informations complémentaires sur la page&lt;/h3&gt;
&lt;p&gt;Vous avez été nombreux à nous demander de voir autre chose que
les URL des pages dans votre interface et notamment de voir
les titres et descriptions.&lt;/p&gt;
&lt;p&gt;C'est maintenant le cas et ceux ci seront renseignés lors du prochain
audit de votre site.&lt;/p&gt;
&lt;h3&gt;Et bien plus&lt;/h3&gt;
&lt;p&gt;Nous avons aussi amélioré bien d'autres points, comme la consommation
mémoire, la prise en compte de la demande d'arrêt d'un audit, etc…&lt;/p&gt;
&lt;p&gt;Nous espérons que cette nouvelle version vous permettra de gérer vos
sitemap encore plus simplement.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Il y a des conférences qui ne se manquent pas, voici Take Off</title>
    <id>https://martin.catty.fr/blog/il-y-a-des-conferences-qui-ne-se-manquent-pas-voici-take-off/</id>
    <link href="https://martin.catty.fr/blog/il-y-a-des-conferences-qui-ne-se-manquent-pas-voici-take-off/" rel="alternate"/>
    <updated>2012-12-15T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Certes ce billet pourrait s'apparenter à du copinage, mais il n'en est rien.&lt;/p&gt;
&lt;p&gt;Quand Florian, Benjamin et Robin sont venus présenter &lt;a href="http://takeoffconf.com/"&gt;Take Off&lt;/a&gt; à un &lt;a href="http://www.synbioz.com/blog/2012/10/23/les_aperos_ruby_nord_une_dynamique_en_marche"&gt;apéro ruby&lt;/a&gt;,
j'avais déjà perçu quelques signes avant coureur sur Twitter.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://takeoffconf.com/"&gt;Take Off&lt;/a&gt; a l'ambitieux objectif de réunir un panel de speakers qui nous parleront
aussi bien de développement front que back et résolument tourné vers les technologies
récentes ou qui vont percer dans le courant 2013.&lt;/p&gt;
&lt;p&gt;Les conférences se feront en anglais pour être accessibles au plus grand nombre et
aussi parce que le lineup réunit plusieurs intervenants non francophones.&lt;/p&gt;
&lt;h3&gt;Une conférence éclectique&lt;/h3&gt;
&lt;p&gt;Ce qui m'a immédiatement séduit c'est l'approche ouverte de la conférence.
 Il ne s'agit pas de venir de se focaliser sur un outil ou un langague mais de s'intéresser
 à tout ce qui se fait et se fera de mieux.&lt;/p&gt;
&lt;p&gt;Les échanges s'annoncent passionnant.&lt;/p&gt;
&lt;h3&gt;Prix&lt;/h3&gt;
&lt;p&gt;Les organisateurs ont réussi le tour de force de réunir une lineup de folie à un prix
absolument abordable.&lt;/p&gt;
&lt;p&gt;On parle certes de centaines d'euros mais il faut imaginer un peu ce que ça coûte de réunir
tout ce beau monde. D'autant plus que le programme comprend la restauration.&lt;/p&gt;
&lt;p&gt;Bref, un réel tour de force. Pour ceux qui sont encore hésitants, pensez au prix que vous mettez
pour acheter une place de concert ou vous avez juste 2H de show.&lt;/p&gt;
&lt;p&gt;Là on parle de 2 jours avec plusieurs intervenants de haut vol.&lt;/p&gt;
&lt;p&gt;Je trouve même que le prix startup ou freelance ne devrait pas exister en tant que tel, mais plutôt être un
prix pour les particuliers, c'est à dire ceux qui iront sur leur propres deniers.&lt;/p&gt;
&lt;h3&gt;Lille&lt;/h3&gt;
&lt;p&gt;Comment à la fois râler que tous les événements soit centralisés à Paris et ne pas participer à une conférence
chez nous ?&lt;/p&gt;
&lt;p&gt;Les 2 journées permettront aussi de faire découvrir Lille.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Même si le on refera pendant et après, on peut d'ores et déjà féliciter les organisateurs de nous
proposer ces 2 jours de conférences.&lt;/p&gt;
&lt;p&gt;Chez Synbioz nous avons hâte d'y être!&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Lancement en bêta de seetemap</title>
    <id>https://martin.catty.fr/blog/lancement-en-beta-de-seetemap/</id>
    <link href="https://martin.catty.fr/blog/lancement-en-beta-de-seetemap/" rel="alternate"/>
    <updated>2012-11-08T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Après quelques temps de gestation nous avons le plaisir d'ouvrir les portes de
notre &lt;a href="https://seetemap.com"&gt;outil de création de sitemap&lt;/a&gt;: &lt;a href="https://seetemap.com"&gt;seetemap.com&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;L'existant en gestion de sitemap&lt;/h3&gt;
&lt;p&gt;Initialement nous avons créé seetemap pour nous car nous n'étions
pas satisfaits des solutions existantes de génération de sitemap.&lt;/p&gt;
&lt;p&gt;Concernant les solutions locales, elles sont vites limitées. Soit elles tentent
de tout faire automatiquement mais ne sont pas capables de détecter toutes vos pages,
soit elles vous obligent à déclarer manuellement votre contenu, ce qui s'avère vite fastidieux.&lt;/p&gt;
&lt;p&gt;Quant aux solutions distantes elles proposent un audit one-shot, avec
souvent un nombre de pages limité, au delà duquel il faut payer.&lt;/p&gt;
&lt;p&gt;Nous avons voulu créer une solution qui permette à votre sitemap de se mettre
à jour tout seul quand votre contenu évolue, sans que vous n'aillez rien à faire, et quelque
soit la taille de votre site.&lt;/p&gt;
&lt;h3&gt;Sur notre application seetemap&lt;/h3&gt;
&lt;p&gt;L'usage de l'outil se veut très simple. Inscrivez vous, indiquez l'URL de votre site
et attendez que l'audit se termine.&lt;/p&gt;
&lt;p&gt;La priorité de vos pages est automatiquement calculée en fonction de leur profondeur et
la fréquence de mise à jour s'adaptera automatiquement au fil des audits.&lt;/p&gt;
&lt;p&gt;Vous pouvez également modifier manuellement ces informations, tout comme vous pouvez retirer une page de votre sitemap.&lt;/p&gt;
&lt;p&gt;Mieux, vous pouvez rechercher des pages et les supprimer à la volée (ex: toutes les pages
contenant ?page=). Cela créera une règle qui sera &lt;strong&gt;automatiquement&lt;/strong&gt; appliquée lors
des prochains audits, excluant les nouvelles pages qui correspondent à votre règle.&lt;/p&gt;
&lt;p&gt;Les audits se font automatiquement à une fréquence déterminée par votre type de compte.
D'une fois tous les 3 jours pour les comptes gratuits, à une génération à la demande pour
les comptes business. Vous pouvez trouver une &lt;a href="https://seetemap.com/fr/formules"&gt;synthèse des différentes formules sur le site&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Sur votre site&lt;/h3&gt;
&lt;p&gt;Pour le moment nous avons créé un module client pour nos amis rubyistes (qui l'eût cru ?),
qui permet de mettre à jour automatiquement le sitemap de votre site.&lt;/p&gt;
&lt;p&gt;D'autres sont à venir pour différents langages et plateformes, mais vous pouvez
quand même télécharger les sitemap et les déposer sur votre site, quelque
soit la technologie que vous utilisez.&lt;/p&gt;
&lt;p&gt;Nous avons donc publié un &lt;a href="http://rubygems.org/gems/seetemap-client"&gt;gem seetemap&lt;/a&gt; que vous aurez juste à inclure dans votre Gemfile.
Il est compatible avec toute application basée sur rack et pas uniquement rails.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://seetemap.com/fr/documentation"&gt;La procédure d'installation de seetemap-client est décrite sur le site&lt;/a&gt;,
que ce soit pour rails ou sinatra.&lt;/p&gt;
&lt;h3&gt;Avis à la communauté&lt;/h3&gt;
&lt;p&gt;En premier lieu, merci à tous ceux qui nous ont fait leurs retours pendant la première phase
de développement du site. Ils ont été précieux.&lt;/p&gt;
&lt;p&gt;Nous avons beaucoup d'idées d'améliorations mais nous souhaitons d'abord &lt;a href="https://seetemap.com/fr/contact"&gt;écouter vos retours&lt;/a&gt;,
et nous assurer que notre produit améliore votre quotidien.&lt;/p&gt;
&lt;p&gt;Pour cela nous avons décidé d'offrir la formule de leur choix à nos 50 premiers testeurs, &lt;a href="https://seetemap.com/fr/users/sign_up"&gt;inscrivez vous vite&lt;/a&gt;!&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Installer Ruby on Rails sur mac</title>
    <id>https://martin.catty.fr/blog/installer-ruby-on-rails-sur-mac/</id>
    <link href="https://martin.catty.fr/blog/installer-ruby-on-rails-sur-mac/" rel="alternate"/>
    <updated>2012-10-18T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;C'est un sujet récurrent lorsqu'on souhaite se mettre au Ruby on Rails:
comment installer un environnement Ruby on Rails sur sa machine ?&lt;/p&gt;
&lt;p&gt;Il existe de nombreux tutoriels pour installer Ruby on Rails mais les outils changent et s'améliorent au fil du temps.
Les documentations sont donc parfois obsolètes.&lt;/p&gt;
&lt;p&gt;Cet article couvre l'installation sous Mac uniquement. Elle a été testée depuis une machine virtuelle
vierge sous Mountain Lion, mais devrait fonctionner tout aussi bien sur des versions plus anciennes d'OSX.&lt;/p&gt;
&lt;p&gt;Si vous souhaitez suivre cette &lt;a href="https://hackademy.io/tutoriel-videos/installer-ruby-on-rails-mac-os-x"&gt;installation de ruby on rails sur mac en vidéo&lt;/a&gt;, rendez vous sur la vidéo
associées sur &lt;a href="https://hackademy.io/tutoriel-videos/installer-ruby-on-rails-mac-os-x"&gt;Hackademy&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Installer XCode&lt;/h3&gt;
&lt;p&gt;XCode ne vous servira pas en temps que tel mais vous permettra d'installer simplement
les outils de devéloppement (gcc…) nécessaires à la compilation de ruby.&lt;/p&gt;
&lt;p&gt;Vous pouvez l'installer depuis l'app store.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/xcode.png"&gt;&lt;img alt="Installation de XCode et des outils en ligne de commande" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/xcode_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Installer un gestionnaire de paquet&lt;/h3&gt;
&lt;p&gt;Il y a quelques temps 2 gestionnaires de paquets étaient disponibles sous Mac OS X: Fink et MacPorts.&lt;/p&gt;
&lt;p&gt;Si vous utilisez l'un des deux assurez vous de le désinstaller.&lt;/p&gt;
&lt;p&gt;À l'heure actuelle c'est &lt;a href="http://mxcl.github.com/homebrew/"&gt;Homebrew&lt;/a&gt; qui est le plus maintenu. Qui plus est, les formules permettant
d'installer des packages sont écrites en ruby, ne boudons pas notre plaisir.&lt;/p&gt;
&lt;p&gt;Pour installer brew, votre compte a besoin des privilèges administrateur:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/homebrew_1.png"&gt;&lt;img alt="Installation de Homebrew" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/homebrew_1_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Installer git&lt;/h3&gt;
&lt;p&gt;Git est un élément central de tous les outils que nous allons utiliser. C'est un outil
de versioning de code source.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;brew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/installation_git_1.png"&gt;&lt;img alt="Installation de Git" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/installation_git_1_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Installer rbenv&lt;/h3&gt;
&lt;p&gt;Les derniers OS X sont livrés avec ruby mais en version 1.8.7, alors que la version actuelle
est la 1.9.3.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/version_ruby_base.png"&gt;&lt;img alt="Version de base de ruby sous Mac Os X" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/version_ruby_base_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Rbenv permet d'installer plusieurs versions de ruby et d'en changer de façon transparente selon
le projet, et donc de suivre les versions les plus à jour pour les projets les plus récents.&lt;/p&gt;
&lt;p&gt;Mettons d'abord à jour les formules de Homebrew afin d'installer les dernières versions des outils voulus.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;puis installons rbenv:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;rbenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;rbenv va nous permettre d'utiliser plusieurs versions de ruby mais n'inclut pas de base l'outil pour
installer des versions spécifiques de ruby. C'est ruby-build qui s'en charge:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;ruby-build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Afin que rbenv soit disponible dans notre shell, il a besoin de s'initialiser au lancement du shell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'if which rbenv &amp;gt; /dev/null; then eval "$(rbenv init -)"; fi'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Une fois réalisé, ouvrez un nouvel onglet dans le terminal pour que le paramétrage soit pris en compte.&lt;/p&gt;
&lt;h3&gt;Installer ruby&lt;/h3&gt;
&lt;p&gt;Maintenant que rbenv est installé, nous allons pouvoir installer la version de ruby voulue.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rbenv&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.9.3-p194
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/installation_ruby_1.9.3-p194.png"&gt;&lt;img alt="Installation de Ruby 1.9.3-p194" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/installation_ruby_1.9.3-p194_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Définissons cette version comme la version par défaut plutôt que la version 1.8.7 du système:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rbenv&lt;span class="w"&gt; &lt;/span&gt;global&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.9.3-p194
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Installer bundler&lt;/h3&gt;
&lt;p&gt;L'environnement ruby est composé de gems, qui sont des libs, voire plus, packagées.
Bundler est un outil permettant de gérer les dépendances et les versions de ces gems.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gem&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;bundler
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Installer Ruby on Rails&lt;/h3&gt;
&lt;p&gt;Installons la dernière version de rails:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gem&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;rails
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/installation_rails.png"&gt;&lt;img alt="Installation de Ruby on Rails 3.2.8" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/installation_rails_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Au besoin nous pouvons installer une version spécifique, par exemple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gem&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;rails&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;On le voit à l'installation, Ruby on Rails dépend de nombreuses gems.&lt;/p&gt;
&lt;p&gt;Quand vous installez une gem qui mettra à disposition une commande,
il est important de faire un &lt;code&gt;rbenv rehash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Une fois installée, la commande est disponible, vous êtes prêt à créer
votre première application Ruby on Rails:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rails&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/creation_application_rails.png"&gt;&lt;img alt="Création d'application Ruby on Rails" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/creation_application_rails_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Spécifier la version de ruby de notre application Rails&lt;/h3&gt;
&lt;p&gt;Indiquer la version de ruby utilisée est très simple, il suffit
de créer un fichier .rbenv-version dans le projet, contenant le numéro
de version.&lt;/p&gt;
&lt;p&gt;Si je me déplace dans un dossier contenant ce fichier, c'est la version
définie qui prendra le pas sur la version globale.&lt;/p&gt;
&lt;p&gt;Si par exemple dans mon projet je crée un fichier avec la dernière version sortie (1.9.3-p286),
&lt;code&gt;ruby -v&lt;/code&gt; m'indiquera que la version n'est pas installée.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/version_ruby_non_installee.png"&gt;&lt;img alt="Création d'application Ruby on Rails" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/version_ruby_non_installee_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nous espérons que ce pas à pas vous permettra de débuter sous Rails.
Pour toute question n'hésitez pas à laisser un commentaire.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Le mécanisme de session en rails</title>
    <id>https://martin.catty.fr/blog/le-mecanisme-de-session-en-rails/</id>
    <link href="https://martin.catty.fr/blog/le-mecanisme-de-session-en-rails/" rel="alternate"/>
    <updated>2012-10-04T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Le fonctionnement d'HTTP&lt;/h3&gt;
&lt;p&gt;HTTP est un protocole sans état. Il n'inclue pas de notion d'identité de base et chaque requête
est donc indépendante.&lt;/p&gt;
&lt;p&gt;Cela signifie que le serveur ne me reconnait pas entre chaque requête.&lt;/p&gt;
&lt;p&gt;C'est la base du fonctionnement du web, et l'opposé d'une connexion client - serveur tel que FTP.&lt;/p&gt;
&lt;p&gt;L'avantage est que le serveur n'a pas à gérer ses clients, tout le monde est logé à la même enseigne.
L'inconvénient est que l'ajout d'un mécanisme d'identification va ajouter un surplus d'information à chaque requête.&lt;/p&gt;
&lt;h3&gt;Les sessions, ou comment transformer HTTP en protocole à état&lt;/h3&gt;
&lt;p&gt;Le mécanisme de session prend place à la fois côté client et serveur. Il est activé de base sur une
application Rails par le biais d'un &lt;a href="http://www.synbioz.com/blog/2012/03/21/les_middlewares_fondations_de_rails"&gt;middleware&lt;/a&gt; (ActionDispatch::Session::CookieStore) qui peut être désactivé
au besoin.&lt;/p&gt;
&lt;p&gt;Lorsqu'un nouvel utilisateur arrive sur l'application, si le serveur ne reçoit pas d'identifiant
de session il va en créer un qu'il va transmettre dans sa réponse.&lt;/p&gt;
&lt;p&gt;Côté client cet identifiant va être stocké dans un cookie. Les cookies ne sont pas forcément des fichiers plats.
Sous chrome les cookies sont stockés dans une base SQLite et sont limités à 4ko en contenu.&lt;/p&gt;
&lt;p&gt;Il est fortement déconseillé de placer des objets en session car cela pourrait poser des soucis d'encodage
/ décodage.&lt;/p&gt;
&lt;p&gt;Par exemple on mettra un id utilisateur plutôt que l'objet utilisateur. Qui plus est, cela permet de limiter
l'espace occupé par le cookie.&lt;/p&gt;
&lt;h3&gt;Les sessions avec Ruby on Rails&lt;/h3&gt;
&lt;p&gt;Ruby on rails possède plusieurs mécanismes de stockage de session. Par défaut il utilise le cookie store qui
stocke tout sur le client et ne nécessite aucune configuration.&lt;/p&gt;
&lt;p&gt;Dans ce cas l'id session n'a pas d'intérêt pour la partie serveur.&lt;/p&gt;
&lt;p&gt;Ce paramètre peut être modifié dans &lt;code&gt;config/initializers/session_store.rb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;App::Application.config.session_store :cookie_store, :key =&amp;gt; '_app_application_session'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Les autres mécanismes de stockage sont la base de données et le cache. Dans ces cas
l'id session va permettre au serveur de retrouver les données associées en base ou en cache.&lt;/p&gt;
&lt;p&gt;L'avantage de ces mécanismes est de pouvoir stocker plus de données voir d'améliorer
les performances dans le cas du cache.&lt;/p&gt;
&lt;p&gt;Attention toutefois, si votre serveur de cache vient à tomber vous n'avez plus d'utilisateur connectés.
D'autre part ces mécanismes impliquent une purge côté applicatif si vous ne voulez pas stocker des miliers
de sessions fantômes.&lt;/p&gt;
&lt;p&gt;Dernier point, vous aurez besoin de partager ces sessions entre serveurs si vous mettez en place du load balancing sur votre
application.&lt;/p&gt;
&lt;h3&gt;Le contenu des cookies&lt;/h3&gt;
&lt;p&gt;Le contenu des cookies est encodé avec le secret de l'application rails, qui peut être surchargé dans votre initializer avec l'option &lt;code&gt;secret_token&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Voici un exemple de contenu de cookie:&lt;/p&gt;
&lt;pre&gt;
BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJWNhZTFlMjY0YWRlZTc2NjNhZDc4YzY4YzkwMzk3NWVlBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMXBEZlQrdkVXZndjdGpQd1JSNTRQUjhYWlp6WHFldUlZYURZZklkYmh4UVE9BjsARkkiGXdhcmRlbi51c2VyLnVzZXIua2V5BjsAVFsISSIJVXNlcgY7AEZbBmkE8NcBLUkiIiQyYSQxMCRMUXNTdU9rbk4wSVIySUZORHFESVcuBjsAVA==--f58cf55b4d11b47c398103643b0ffe7ffdbff309
&lt;/pre&gt;

&lt;p&gt;Le cookie présente 2 parties, séparées par --, d'un côté le contenu et de l'autre côté la signature. Séparons les:&lt;/p&gt;
&lt;pre code="irb"&gt;
  content = "BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJWNhZTFlMjY0YWRlZTc2NjNhZDc4YzY4YzkwMzk3NWVlBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMXBEZlQrdkVXZndjdGpQd1JSNTRQUjhYWlp6WHFldUlZYURZZklkYmh4UVE9BjsARkkiGXdhcmRlbi51c2VyLnVzZXIua2V5BjsAVFsISSIJVXNlcgY7AEZbBmkE8NcBLUkiIiQyYSQxMCRMUXNTdU9rbk4wSVIySUZORHFESVcuBjsAVA=="
  signature = "f58cf55b4d11b47c398103643b0ffe7ffdbff309"
&lt;/pre&gt;

&lt;p&gt;À la réception du cookie, rails va vérifier que le contenu et la signature correspondent. Pour cela il va utiliser son secret token.
&lt;strong&gt;Note&lt;/strong&gt;: vous ne devez jamais divulguer ce token où vos sessions seront usurpables.&lt;/p&gt;
&lt;p&gt;Voyons quel est le secret de notre app de démo:&lt;/p&gt;
&lt;pre code="irb"&gt;
secret = Rails.application.config.secret_token
"0b22c3a50d0ba81e396b880268d8d13c5980896a3761a24bc87e704f3589f9d0ef2528ef7480101e0edeccc5320f6310d54fd06ad7df574ee6e8e349ba01ace2"
&lt;/pre&gt;

&lt;p&gt;Voilà ce que fait rails pour générer la signature:&lt;/p&gt;
&lt;pre code="irb"&gt;
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, str)
"f58cf55b4d11b47c398103643b0ffe7ffdbff309"
&lt;/pre&gt;

&lt;p&gt;Effectivement, on retombe bien sur notre signature.&lt;/p&gt;
&lt;p&gt;Par le biais de ce mécanisme, modifier le contenu du cookie ne permet pas d'usurper une session car la signature ne sera
plus valable et notre framework s'en rendra compte.&lt;/p&gt;
&lt;p&gt;Attention, si les cookies sont signés leur contenu n'est toutefois pas encrypté.&lt;/p&gt;
&lt;p&gt;Il est tout à fait possible d'extraire le contenu d'un cookie.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Marshal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CGI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="s2"&gt;"session_id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cae1e264adee7663ad78c68c903975ee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="s2"&gt;"_csrf_token"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pDfT+vEWfwctjPwRR54PR8XZZzXqeuIYaDYfIdbhxQQ="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"warden.user.user.key"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;755095536&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$2a$10$LQsSuOknN0IR2IFNDqDIW."&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;J'espère que cet article vous aura permi de mieux appréhender le fonctionnement des sessions, notamment dans Ruby on Rails.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>ECMAScript 6</title>
    <id>https://martin.catty.fr/blog/ecmascript-6/</id>
    <link href="https://martin.catty.fr/blog/ecmascript-6/" rel="alternate"/>
    <updated>2012-08-29T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;&lt;a href="http://www.ecmascript.org/"&gt;ECMAScript&lt;/a&gt; est un langage de script standardisé.
Javascript est une implémentation de ce standard.&lt;/p&gt;
&lt;p&gt;La version actuellement en cours de discussion est &lt;a href="http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts"&gt;ECMAScript 6&lt;/a&gt;, dite harmony.&lt;/p&gt;
&lt;p&gt;Comme toute bonne spec elle évolue en fonction des usages et des besoins.
ECMAScript s'inspire donc notamment de &lt;a href="http://www.synbioz.com/blog/2011/08/02/introduction_a_coffeescript"&gt;coffeescript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Comme toute (bonne) spec elle met aussi beaucoup de temps à être standardisée.
Pour ces raisons, les différents navigateurs implémentent des versions non finalisées d'ECMAScript.&lt;/p&gt;
&lt;p&gt;Faisons un petit tour d'horizon des nouveautés d'ECMAScript 6.&lt;/p&gt;
&lt;h3&gt;Variables locales&lt;/h3&gt;
&lt;p&gt;Il est maintenant possible de définir des variables locales à un bloc (let, const),
ce qui évite des conflits potentiels de variables déclarées avec var.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// b is not defined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Arguments par défaut&lt;/h3&gt;
&lt;p&gt;Les fonctions permettent maintenant de passer des arguments par défaut.
Quant au chaines elles supportent l'interpolation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;greetings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synbioz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`Hello &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;!`&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Déstructuration&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// a == 1&lt;/span&gt;
&lt;span class="c1"&gt;// b == 2&lt;/span&gt;
&lt;span class="c1"&gt;// c == 3&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// a == 1&lt;/span&gt;
&lt;span class="c1"&gt;// b == 2&lt;/span&gt;
&lt;span class="c1"&gt;// c == [3, 4]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;À l'image des splats en coffescript les &lt;code&gt;...&lt;/code&gt; permettent de récupérer le reste des arguments.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// ne stocke pas le 2&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// swap&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Chaînes de caractères sur plusieurs lignes&lt;/h3&gt;
&lt;p&gt;Les utilisateurs d'azerty risquent de ticker devant le caractère choisi (backtick),
mais ce sera une excellente raison de passer au bépo.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`hello&lt;/span&gt;
&lt;span class="sb"&gt;  i'm talking&lt;/span&gt;
&lt;span class="sb"&gt;  to you.&lt;/span&gt;
&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Sucre syntaxique&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 3, 8, 12&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"even"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note: &lt;code&gt;for of&lt;/code&gt; n'a pas vocation à remplacer &lt;code&gt;for in&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Proxies&lt;/h3&gt;
&lt;p&gt;Les proxies offre une interface de méta-programmation.
Ils permettent de définir des méthodes et autres propriétés objets à la volée.&lt;/p&gt;
&lt;p&gt;Pour rapprocher cela de ruby il faut imaginer des choses comme &lt;code&gt;Class.new&lt;/code&gt;, &lt;code&gt;define_method&lt;/code&gt;,
&lt;code&gt;method_missing&lt;/code&gt;…&lt;/p&gt;
&lt;p&gt;Les usages sont plutôt rares, je vous invite à regarder cet &lt;a href="https://gist.github.com/1202328"&gt;exemple de générateur HTML&lt;/a&gt; avec
des appels de méthodes chainés.&lt;/p&gt;
&lt;p&gt;Quand le proxy va recevoir un appel de méthode, c'est le get du proxy qui sera appelé avec
en paramètre l'objet et le nom de la méthode.&lt;/p&gt;
&lt;p&gt;Ici &lt;code&gt;get&lt;/code&gt; va vérifier que le nom de la méthode fait partie des tags et agir en conséquence.&lt;/p&gt;
&lt;h3&gt;Weak maps&lt;/h3&gt;
&lt;p&gt;Les weak maps s'apparentent à un hash dont les clés sont des objets.&lt;/p&gt;
&lt;p&gt;Les éléments auxquels on n'accède pas depuis longtemps sont automatiquement collectés par le ramasse miette.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;WeakMap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

&lt;span class="c1"&gt;// Ici o n'existera plus en mémoire.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;L'intérêt de ce système est d'éviter les fuites mémoires d'objets qui seraient dans
un hash classique et qui auraient une référence circulaire (et donc ne serait jamais
collectés par le ramasse miette).&lt;/p&gt;
&lt;p&gt;Les weak map peuvent aussi permettre d'étendre un objet global dans un contexte défini,
sans risque de collision.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// avant: aucune garantie que l'on n'écrase pas une valeur existante.&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;

&lt;span class="c1"&gt;// après: plus de risque de collision&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;WeakMap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'bar'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Générateurs&lt;/h3&gt;
&lt;p&gt;Il s'agit de fonctions interruptibles, un exemple ici d'implémentation de fibonacci avec:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Structure de données&lt;/h3&gt;
&lt;p&gt;ECMAScript 6 offre la possibilité de définir ses propres structures de données,
en utilisant des données binaires.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Point2D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Pixel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Point2D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Color, Pixel et Point2D agissent tous les 3 comme des objets.
Ils permettent de définir le cadre des futures instances.&lt;/p&gt;
&lt;h3&gt;Approche objet&lt;/h3&gt;
&lt;p&gt;ECMA 6 offre des mécanismes natifs de gestion objet, comme la définition
de classe, l'héritage…&lt;/p&gt;
&lt;p&gt;Un exemple de définition d'une classe:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Modules&lt;/h3&gt;
&lt;p&gt;ECMAScript 6 propose un système de module qui permettra de factoriser
le code et importer les éléments définis à divers endroits.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.141593&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ce système va s'avérer très pratique pour les libs telles que &lt;a href="http://www.synbioz.com/blog/2011/11/03/introduction_a_mootools"&gt;MooTools&lt;/a&gt;, jQuery
ou autre.&lt;/p&gt;
&lt;h3&gt;Variable privées&lt;/h3&gt;
&lt;p&gt;À l'heure actuelle pour définir des &lt;a href="http://www.synbioz.com/blog/2011/05/17/le_cote_objet_de_javascript"&gt;variables privées en javascript&lt;/a&gt; dans une classe,
voilà le code qu'on doit utiliser:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// private var&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_foo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le problème de cette solution est qu'elle n'est pas efficace car la méthode
d'accès (fonction this.foo) sera régénérée au runtime à chaque instance créée,
alors que le traitement est toujours le même.&lt;/p&gt;
&lt;p&gt;ECMAScript 6 introduit donc un nouveau concept pour la gestion des propriétés privées:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"awesome_key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Avec ce système on n'a pas de visiblité sur le nom de la propriété en elle même.&lt;/p&gt;
&lt;p&gt;Cette présentation d'ECMAScript 6 vous donne un avant goût du potentiel à venir
de javascript.&lt;/p&gt;
&lt;p&gt;Évidemment il est impossible de prédire quand cette version sera disponible dans nos navigateurs,
encore plus quand elle le sera dans tous les navigateurs ; mais cela vous donne un avant goût
de ce que nous réserve javascript dans les versions à venir.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Envoyer des notifications iOS en ruby</title>
    <id>https://martin.catty.fr/blog/envoyer-des-notifications-ios-en-ruby/</id>
    <link href="https://martin.catty.fr/blog/envoyer-des-notifications-ios-en-ruby/" rel="alternate"/>
    <updated>2012-07-24T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;Le besoin&lt;/h3&gt;
&lt;p&gt;Par défaut Mail sur iPhone est incapable de relever automatiquement les emails dans les sous
dossiers, pour les comptes de type imap.&lt;/p&gt;
&lt;p&gt;Comme j'utilise des tris serveur avec Dovecot c'est particulièrement fastidieux de vérifier
chaque sous dossier à la main en situation de mobilité.&lt;/p&gt;
&lt;p&gt;Je me suis donc dit qu'il serait pratique d'avoir une notification pour ces emails.&lt;/p&gt;
&lt;h3&gt;Pré-requis&lt;/h3&gt;
&lt;p&gt;Tant qu'à faire je souhaitais résoudre ce problème en ruby. Heureusement pour nous &lt;a href="https://github.com/highgroove/grocer"&gt;Grocer&lt;/a&gt;
est là.&lt;/p&gt;
&lt;p&gt;Pour envoyer des notifications il va nous falloir deux choses importantes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un compte developer Apple, afin d'obtenir un certificat permettant de délivrer des notifications.&lt;/li&gt;
&lt;li&gt;Le token du device sur lequel envoyer les notifications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce dernier est différent de l'UDID de l'appareil. Pour le récupérer nous allons devoir créer une mini
application iOS et l'installer sur l'appareil. C'est expliqué sur le dépôt de Grocer.&lt;/p&gt;
&lt;p&gt;Il s'agit uniquement de demander à l'utilisateur l'autorisation pour envoyer des notifications et
d'afficher le token dans le log.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// AppDelegate.m&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;BOOL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;UIApplication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;didFinishLaunchingWithOptions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;NSDictionary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;launchOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NSLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"Registering for remote notifications"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="bp"&gt;UIApplication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sharedApplication&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registerForRemoteNotificationTypes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIRemoteNotificationTypeAlert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UIRemoteNotificationTypeBadge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UIRemoteNotificationTypeSound&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;YES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;UIApplication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;didRegisterForRemoteNotificationsWithDeviceToken&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;NSData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;deviceToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NSLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"deviceToken: %@"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deviceToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;UIApplication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;didFailToRegisterForRemoteNotificationsWithError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;NSError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NSLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"Error in registration. Error: %@"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Une fois l'application buildée et installée, il suffit de la lancer et de consulter les logs du terminal depuis la section
organizer de Xcode.&lt;/p&gt;
&lt;p&gt;Le fameux token devrait s'y retrouver.&lt;/p&gt;
&lt;h3&gt;Le nez dans le code&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/fuse/isn"&gt;Le code d'envoi des notifications&lt;/a&gt; se trouve sur Github.&lt;/p&gt;
&lt;h4&gt;Paramétrage&lt;/h4&gt;
&lt;p&gt;Pour commencer il faut paramétrer l'application dans &lt;code&gt;config/global.yml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;On peut y mettre plusieurs boîtes. Par défaut j'utilise le format qu'on retrouve pour les DNS,
à savoir &lt;a href="&amp;#109;&amp;#97;&amp;#105;&amp;#108;&amp;#116;&amp;#111;&amp;#58;&amp;#102;&amp;#111;&amp;#111;&amp;#64;&amp;#101;&amp;#120;&amp;#97;&amp;#109;&amp;#112;&amp;#108;&amp;#101;&amp;#46;&amp;#98;&amp;#97;&amp;#114;"&gt;&amp;#102;&amp;#111;&amp;#111;&amp;#64;&amp;#101;&amp;#120;&amp;#97;&amp;#109;&amp;#112;&amp;#108;&amp;#101;&amp;#46;&amp;#98;&amp;#97;&amp;#114;&lt;/a&gt; devient foo.example.bar&lt;/p&gt;
&lt;p&gt;Une fois au moins un compte imap configuré, il reste à coller le certificat d'envoi de notifications dans le dossier &lt;code&gt;certs&lt;/code&gt;
avec le nom &lt;code&gt;certificate.pem&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On peut décider de ne pas notifier l'arrivée de mail dans certaines boîtes avec skip.&lt;/p&gt;
&lt;h4&gt;Comment ça marche?&lt;/h4&gt;
&lt;p&gt;Pour éviter plusieurs notifications concernant les même emails (même s'ils n'ont pas été lus),
nous allons stocker les identifiants des emails.&lt;/p&gt;
&lt;p&gt;Pour cela nous utilisons une base de données SQLite avec une unique table où nous allons stocker
le nom de la boite (sous la forme foo.example.bar.folder.subfolder) et les identifiants des messages non lus.&lt;/p&gt;
&lt;p&gt;Le parcours des différents sous dossiers des différentes boites se fait avec &lt;code&gt;net/imap&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lorsque la différence entre ids des messages non lus (ex: 3,4,5) et ids stockés en base (ex: 1,2,3) renvoie des valeurs (4, 5) nous envoyons
une notification.&lt;/p&gt;
&lt;p&gt;À noter donc que la notification se fait par sous dossier. On peut donc potentiellement recevoir plusieurs emails
à la suite.&lt;/p&gt;
&lt;p&gt;Voilà pour une introduction à la mise en place de Grocer, qui remplit parfaitement son rôle.
Pour un tour d'horizon complet des différentes options je vous invite à aller jeter un œil sur le Github.&lt;/p&gt;
&lt;p&gt;Autrement, vos commentaires sont les bienvenus!&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Création de données et validations avec Backbone.js</title>
    <id>https://martin.catty.fr/blog/creation-de-donnees-et-validations-avec-backbone.js/</id>
    <link href="https://martin.catty.fr/blog/creation-de-donnees-et-validations-avec-backbone.js/" rel="alternate"/>
    <updated>2012-06-19T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Après notre &lt;a href="http://www.synbioz.com/blog/2012/04/12/presentation_de_backbone_js_partie_1"&gt;introduction à Backbone.js&lt;/a&gt;, nous allons voir dans cette partie comment aller plus loin en insérant des données.&lt;/p&gt;
&lt;p&gt;Le &lt;a href="https://github.com/synbioz/backbone-example"&gt;code de cette application Backbone&lt;/a&gt; est disponible sur github.&lt;/p&gt;
&lt;h3&gt;Créer un formulaire en Backbone.js&lt;/h3&gt;
&lt;p&gt;Pour commencer nous allons enrichir notre layout d'un nouveau template javascript.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text/template"&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"new_category"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"category_name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"category[name]"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Type a category…"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"save-new-category"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Enregistrer"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nous allons simplement mettre à disposition un champ et un bouton.&lt;/p&gt;
&lt;p&gt;Côté contrôleur, rien qu'une action create classique. Nous allons simplement préparer nos
messages d'erreur pour un affichage côté client.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="vi"&gt;@category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;respond_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Il nous reste à préparer notre vue Backbone.js.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NewCategoryView&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;tagName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'form'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#new_category'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;events&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;'keypress #category_name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'saveNewCategoryIfSubmit'&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;'click #save-new-category'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'saveNewCategory'&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;'focusout #category_name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'emptyMessages'&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bindAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'render'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'reset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@render&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;render&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;@el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@template&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;saveNewCategoryIfSubmit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(event) -&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;saveNewCategory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;saveNewCategory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#category_name"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@showErrors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;emptyMessages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#messages"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;showErrors&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(model, errors) -&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@emptyMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#messages"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="vi"&gt;@app.NewCategoryView&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NewCategoryView&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Le template sera enrobé dans un formulaire.&lt;/p&gt;
&lt;p&gt;Dans l'absolu nous n'avons pas besoin de passer la collection à cette vue. Nous le faisons
uniquement pour bénéficier de la facilité du create.&lt;/p&gt;
&lt;p&gt;Cela s'apparente à ce qu'on pourrait faire dans le cadre d'une association en Ruby on Rails
(par exemple @category.products.create).&lt;/p&gt;
&lt;p&gt;On pourrait toutefois s'en passer en faisant une initialisation classique du model (new, save).&lt;/p&gt;
&lt;h3&gt;Gérer les évènements avec Backbone.js&lt;/h3&gt;
&lt;p&gt;Dans notre vue nous avons introduit la notion d'évènements.&lt;/p&gt;
&lt;p&gt;La syntaxe se fait tout simplement sous forme d'un hash avec d'une part l'évènement et le sélecteur
et d'autre part la fonction à appeler. L'évènement concerné sera systématiquement passé à cette fonction.&lt;/p&gt;
&lt;p&gt;Notre premier évènement va permettre de détecter les frappes dans notre input et appeler &lt;code&gt;saveNewCategoryIfSubmit&lt;/code&gt;
en retour.&lt;/p&gt;
&lt;p&gt;Dans cette fonction nous allons tester si c'est ou non la touche entrée qui est utilisée. Si c'est le cas,
nous lançons la création de données.&lt;/p&gt;
&lt;p&gt;Nous faisons exactement la même chose lorsque nous cliquons sur le bouton.&lt;/p&gt;
&lt;h3&gt;Créer des données avec Backbone&lt;/h3&gt;
&lt;p&gt;Dans cette partie nous allons voir comment créer des données tout en utilisant les validations
de notre couche client.&lt;/p&gt;
&lt;p&gt;La partie création de données se résume à ces deux lignes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#category_name"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;@collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@showErrors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Pour récupérer nos valeurs nous utilisons simplement la méthode val() de jQuery.
Le create de la collection utilise l'url que nous avons définie dans cette dernière pour poster les données.&lt;/p&gt;
&lt;p&gt;Le deuxième argument est un hash de définition des callbacks.
En cas d'erreur c'est la méthode showErrors qui sera appelée.&lt;/p&gt;
&lt;p&gt;Ce callback va nous passer deux paramètres: l'objet et les erreurs. Comme nous avons
déjà préparé nos erreurs sous forme de string il nous reste juste à les insérer dans la zone de messages,
en l'ayant vidée au préalable.&lt;/p&gt;
&lt;p&gt;Notre dernier évènement focusout va automatiquement vider la zone de message quand nous
allons retirer le focus du champ.&lt;/p&gt;
&lt;h3&gt;Validations client avec Backbone.js&lt;/h3&gt;
&lt;p&gt;Les validations par la couche serveur sont indispensables. Mais dans le
cadre d'une application riche, ce n'est pas du luxe d'ajouter des validations
clientes afin de d'avoir un retour immédiat.&lt;/p&gt;
&lt;p&gt;Cela augmentera le sentiment de vitesse et de confort de l'application.&lt;/p&gt;
&lt;p&gt;Pour faire de la validation côté client, backbone.js nous mets à disposition une
méthode &lt;code&gt;validate&lt;/code&gt;, libre à nous d'implémenter son contenu.&lt;/p&gt;
&lt;p&gt;Validate nous passe les attributs en paramètres, mais attention, uniquement les
paramètres qui ont été modifiés.&lt;/p&gt;
&lt;p&gt;Les attributs de l'objet sont eux stockés dans &lt;code&gt;@attributes&lt;/code&gt;. Nous allons donc
travailler sur un merge des deux pour passer nos validations.&lt;/p&gt;
&lt;p&gt;Pour éviter de merger dans l'une des deux variables nous allons cloner les attributs
de l'objet d'abord en utilisant les méthodes de underscore.js.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(attributes) -&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;mergedAttributes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;@attributes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mergedAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mergedAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBlank&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Name can't be blank."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Désormais le create va appeler les validations sur le modèle.
Il y a très peu de choses à appeler dans notre vue, le même callback
du create sera utilisé avec les mêmes paramètres.&lt;/p&gt;
&lt;p&gt;Nous allons juste l'adapter pour utiliser soit responseText si la
réponse vient du serveur, soit directement errors.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;showErrors&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(model, errors) =&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;@emptyMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#messages"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Rechargement automatique de la liste&lt;/h3&gt;
&lt;p&gt;Pour en terminer avec cette partie une étape importante est de recharger automatiquement
notre liste à l'ajout d'un item.&lt;/p&gt;
&lt;p&gt;C'est là qu'on s'aperçoit de toute la puissance de Backbone.js.
Il va nous suffire d'être à l'écoute du add sur la collection pour refaire un rendu de la liste.&lt;/p&gt;
&lt;pre class ="coffeescript"&gt;
initialize: -&gt;
  _.bindAll(@, 'render')
  @collection.bind 'reset', @render
  @collection.bind 'add', @render
&lt;/pre&gt;

&lt;p&gt;J'espère que cette partie vous aura aider à mieux appréhender les concepts et la manière
d'utiliser Backbone.js.&lt;/p&gt;
&lt;p&gt;Si vous souhaitez que nous allions plus loin, n'hésitez pas à nous en faire part dans les
commentaires.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Full-text search with sphinx and thinking sphinx</title>
    <id>https://martin.catty.fr/blog/full-text-search-with-sphinx-and-thinking-sphinx/</id>
    <link href="https://martin.catty.fr/blog/full-text-search-with-sphinx-and-thinking-sphinx/" rel="alternate"/>
    <updated>2012-05-18T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;What is full-text search ?&lt;/h3&gt;
&lt;p&gt;Full text search allows you to search through content, or part of a content.&lt;/p&gt;
&lt;p&gt;It's not designed on top of SQL, it uses an indexed DB which is much more efficient than
doing some LIKE queries.&lt;/p&gt;
&lt;p&gt;Search engine uses indexed DB. I'm not telling google is using sphinx but it's based on the
same concept.&lt;/p&gt;
&lt;p&gt;To be able to search some terms, your content must be previously indexed.
Each term of each document is indexed with a position.&lt;/p&gt;
&lt;p&gt;So the main problem of full-text search is to keep an indexed DB up-to-date.
If your content changes very often then you should index it often too.&lt;/p&gt;
&lt;p&gt;But indexing is time consuming, and it will use some resources.
In addition searching should still be available during indexation.&lt;/p&gt;
&lt;h3&gt;Sphinx and Thinking Sphinx&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://sphinxsearch.com/"&gt;Sphinx&lt;/a&gt; is open source search server. Thinking sphinx is a gem built for rails
which interact greatly with it.&lt;/p&gt;
&lt;p&gt;Sphinx must be setup to be able to use Thinking Sphinx. It's available for the 3 main OS.&lt;/p&gt;
&lt;h4&gt;Installing and configuring Thinking Sphinx&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://freelancing-god.github.com/ts/en/"&gt;Thinking Sphinx&lt;/a&gt; can be installed as a gem and configured ìn &lt;code&gt;config/sphinx.yml&lt;/code&gt;
This is an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;development&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;9312&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;enable_star&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;min_infix_len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3&lt;/span&gt;
&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;9313&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;enable_star&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;min_infix_len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can set the port for each environment or add the ability to search terms part.
There is many other options, take a look at the &lt;a href="http://freelancing-god.github.com/ts/en/advanced_config.html"&gt;advanced configuration guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The index has to be rebuilt when changing configuration file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;rake&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;rebuild&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Thinking Sphinx will generate his own config file like &lt;code&gt;config/development.sphinx.conf&lt;/code&gt;
and maintain his indexed db in db/sphinx/development/&lt;/p&gt;
&lt;h4&gt;Indexing&lt;/h4&gt;
&lt;p&gt;As I said before, indexing is not done on the fly when you add new content.
You should explicitly add a routine. Trying to index with model callbacks is not a good idea.&lt;/p&gt;
&lt;p&gt;So you have to choose the better interval between two indexations.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/javan/whenever"&gt;Whenever&lt;/a&gt; is a great gem to automate this job. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;every&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rake&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts:index"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;every&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:reboot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rake&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts:start"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4&gt;What about using sphinx with database filters ?&lt;/h4&gt;
&lt;p&gt;Imagine an e-commerce website. You've got tons of products with descriptions.
Products belongs_to a category, has some pictures and a state (online or not).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"my&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;great&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;product"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"my&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;great&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;description"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;category\_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"online"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If a user browse a category and search a product, we should restrict his search to the scope of this category.
We also want to be able to restrict search to product having pictures and an online state.&lt;/p&gt;
&lt;p&gt;In fact we want to cumulate full-text search and SQL restrictions.&lt;/p&gt;
&lt;p&gt;Thinking sphinx can interact with a SQL database like MySQL.
There is attributes to do that. People often misunderstood difference between indexed fields (used with indexes)
and attributes (used with has).&lt;/p&gt;
&lt;p&gt;Fields are searchable while attributes match exact values. The state and category_id are exact values.
But be careful, you should consider each attribute as an integer, even state which is a string.&lt;/p&gt;
&lt;p&gt;So we're gonna use the CRC32 method to index string as integer and be able to search it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;define_index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# fields to use for term search&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;indexes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;sortable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;indexes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;

&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# attributes usable as sort / filter criteria&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;

&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CRC32(state)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:integer&lt;/span&gt;

&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"COUNT(DISTINCT pictures.id) &amp;gt; 0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:has_pictures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:boolean&lt;/span&gt;

&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pictures&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sphinx_scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:ts_online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"online"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_crc32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The ruby method &lt;code&gt;to_crc32&lt;/code&gt; is not part of the core nor ActiveSupport, it's a thinking sphinx's addition
using &lt;code&gt;Zlib&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Zlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crc32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ruby"&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 3428506893&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Be aware that this is not perfectly reliable as crc32 can have some collisions depending on the term.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;And here we go, we're now able to search products with a particular category, some pictures or not and
specific terms in the title and/or description.&lt;/p&gt;
&lt;p&gt;Sphinx is an easy to setup and powerful solution to add full-text search to your web application.
I really encourage you to dig into the documentation to discover all the available options.&lt;/p&gt;
&lt;p&gt;If you don't want to use Sphinx there is other full-text search engine.
&lt;a href="http://lucene.apache.org/solr/"&gt;solr&lt;/a&gt; is also very popular and has ruby gem like &lt;a href="http://sunspot.github.com/"&gt;sunspot&lt;/a&gt;
to interact with.&lt;/p&gt;
&lt;p&gt;The Synbioz Team.
Free to be together.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Présentation de backbone.js</title>
    <id>https://martin.catty.fr/blog/presentation-de-backbone.js/</id>
    <link href="https://martin.catty.fr/blog/presentation-de-backbone.js/" rel="alternate"/>
    <updated>2012-04-12T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Présentation de Backbone.js&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://backbonejs.org/"&gt;Backbone.js&lt;/a&gt; est un cadre applicatif pour les applications à forte teneur en
javascript.&lt;/p&gt;
&lt;p&gt;Backbone.js peut se définir comme un framework MVC mais pas au sens classique du terme.
Ici le C représente des Collections d'objets. Le M et le V désignent respectivement les
modèles et les vues.&lt;/p&gt;
&lt;p&gt;Backbone.js ne possède qu'une dépendance ferme, il s'agit de &lt;a href="http://underscorejs.org/"&gt;underscore.js&lt;/a&gt;,
qui est maintenu par la même équipe.&lt;/p&gt;
&lt;p&gt;Le rôle d'underscore.js est de vous apporter tout un tas de méthodes pour vous simplifier
la vie (enumerable, binding, template, comparaison…) sans faire 500ko.&lt;/p&gt;
&lt;p&gt;Généralement jQuery vient compléter la liste des dépendances finales de l'application,
car il y a fort à parier que vous aurez à minima besoin de manipuler le DOM.&lt;/p&gt;
&lt;h3&gt;Quand utiliser Backbone.js&lt;/h3&gt;
&lt;p&gt;Backbone.js est un excellent choix pour les applications dites «single page application», c'est
à dire une page principale avec un nombre important d'interactions utilisateur.&lt;/p&gt;
&lt;p&gt;Plutôt que d'avoir une navigation classique par page (ou le serveur envoie toute la page
à chaque URL), la navigation, l'envoi de formulaire et toutes les actions classiques se gèrent en javascript.&lt;/p&gt;
&lt;p&gt;Avant, quand on souhaitait faire ce genre de choses, on écrivait son propre javascript, sans aucune
convention, ầ grand coup d'AJAX et on se retrouvait vite avec du spaghetti code, complexe à maintenir
et à tester.&lt;/p&gt;
&lt;p&gt;Backbone.js nous permet de cadrer cela en définissant modèle, vue et collections.
On va pouvoir définir des événements sur des changements de valeurs de nos modèles et ainsi
rafraichir automatiquement nos vues.&lt;/p&gt;
&lt;p&gt;Pour cela Backbone.js possède son propre routeur. On peut faire correspondre des actions à des URL.&lt;/p&gt;
&lt;p&gt;Le code créé avec Backbone.js peut être testé via &lt;a href="https://jasmine.github.io/"&gt;jasmine&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Ce que Backbone.js ne fait pas&lt;/h3&gt;
&lt;p&gt;Backbone.js est uniquement une couche client, il n'a aucune vocation à gérer la persistence de vos
données sur un serveur.&lt;/p&gt;
&lt;p&gt;Mais rassurez vous cela se fait très bien, il suffit d'indiquer à nos modèles une URL et d'avoir une API
JSON Restful pour être capable de réaliser du &lt;acronym title="Create Read Update Delete"&gt;CRUD&lt;/acronym&gt; très simplement.&lt;/p&gt;
&lt;h3&gt;Apprentissage par la pratique&lt;/h3&gt;
&lt;p&gt;L'objectif de cette première partie est de créer une application qui va lister
les catégories récupérées depuis le serveur.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/synbioz/backbone-example"&gt;L'application backbone.js&lt;/a&gt; est disponible sur github.&lt;/p&gt;
&lt;p&gt;Le tag 0.1 correspond à cette partie.&lt;/p&gt;
&lt;h3&gt;Aperçu rapide de la partie serveur&lt;/h3&gt;
&lt;p&gt;Pour la persistence des données nous utilisons une application Ruby on Rails qui
agit uniquement comme une API.&lt;/p&gt;
&lt;p&gt;Nous avons une migration pour créer des catégories qui ont un nom, ainsi que quelques seeds.
Une fois l'application clonée il vous reste à configurer votre base de données (database.yml) et
lancer le chargement:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rake&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notre contrôleur categories renvoie uniquement du JSON.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;CategoriesController&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ApplicationController&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;respond_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:json&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@categories&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;respond_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@categories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;On va donc pouvoir récupérer nos catégories sur /categories.json&lt;/p&gt;
&lt;h3&gt;La partie client avec Backbone.js et coffeescript&lt;/h3&gt;
&lt;p&gt;Pour son côté pratique et maintenant par habitude, le code est en coffeescript.
Jetez un œil à notre &lt;a href="http://www.synbioz.com/blog/2011/08/02/introduction_a_coffeescript"&gt;introduction à coffeescript&lt;/a&gt;
si vous ne connaissez pas encore. Vous ne serez pas trop dépaysés.&lt;/p&gt;
&lt;p&gt;Le fichier principal est app.coffee, il est généré dans app.js
Si vous souhaitez faire des changements en local pour tester,
vous pouvez utiliser le Guardfile fournit via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;guard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Il s'occupera de compiler votre coffeescript à la volée.&lt;/p&gt;
&lt;h3&gt;Explication du code Backbone.js&lt;/h3&gt;
&lt;p&gt;Logiquement notre application devrait être découplée en plusieurs fichiers,
en séparant modèle, vue etc…&lt;/p&gt;
&lt;p&gt;Ici la simplicité de l'application fait que tout est en un seul fichier.
Commençons à décortiquer le code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Afin que les fichiers javascript puissent avoir connaissance les uns des autres
on utilise l'objet global windows et le namespace app.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;_.templateSettings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;interpolate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sr"&gt;/\{\{\=(.+?)\}\}/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;evaluate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sr"&gt;/\{\{(.+?)\}\}/g&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Underscore.js possède un système de template, dont la syntaxe par défaut
est la même que celle des templates ERB.&lt;/p&gt;
&lt;p&gt;Nous changeons cela pour qu'ERB n'essaie pas de les interpréter, ce sera le
rôle de Backbone.js.&lt;/p&gt;
&lt;p&gt;Pour cela nous modifions les paramètres du template pour avoir une syntaxe
proche de celle de &lt;a href="http://mustache.github.com/"&gt;mustache&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On va donc passer de &amp;lt;% %&amp;gt; à {{ }}.&lt;/p&gt;
&lt;h4&gt;Définition du modèle&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notre catégorie va simplement étendre le modèle de base de
Backbone.js pour hériter de toutes ces méthodes (save…).&lt;/p&gt;
&lt;p&gt;Ici on utilise directement le mécanisme de coffeescript.
Par convention le modèle est au singulier.&lt;/p&gt;
&lt;p&gt;En javascript on aurait plutôt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Une fois notre classe créée nous la mettons dans le namespace.
Nous le ferons systématiquement pour les autres classes, quelque
soit leur fonction.&lt;/p&gt;
&lt;h4&gt;Définition de la collection&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Categories&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Collection&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/categories"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="vi"&gt;@app.Categories&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Categories&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;La collection se trouve à mi chemin entre la vue et le modèle.
Elle concerne un modèle dont elle contiendra une liste, dont
le but sera d'aller peupler une vue.&lt;/p&gt;
&lt;p&gt;Par convention la collection est au pluriel.&lt;/p&gt;
&lt;p&gt;Partant de là nous sommes déjà en mesure de récupérer les données de l'API.
Vous pouvez tester depuis la console de votre navigateur:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Categories&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/log.png"&gt;&lt;img alt="Liste des catégories" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/log_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Définition des vues&lt;/h4&gt;
&lt;p&gt;Regardons comment définir un template dans nos vues HTML.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text/template"&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Les templates sont définis dans le head de notre HTML, de base
le navigateur n'interprétera rien.
Comme mentionné plus tôt nous utilisons la syntaxe de mustache.&lt;/p&gt;
&lt;p&gt;Le but pour ces templates est d'être remplacé par le HTML correspondant
à ce qu'on souhaite afficher par Backbone.js.&lt;/p&gt;
&lt;p&gt;Par exemple #category représentera un élément LI contenant le nom
de la catégorie.&lt;/p&gt;
&lt;p&gt;Voyons comment est définie notre classe du côté de Backbone.js:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CategoryView&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;tagName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'li'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#category'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bindAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'render'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;render&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;@model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;
&lt;span class="vi"&gt;@app.CategoryView&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CategoryView&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nous spécifions donc le tagName qui servira à l'enrobage HTML. Il
existe d'autres options comme la classe CSS…&lt;/p&gt;
&lt;p&gt;Ensuite nous indiquons quel est le template à utiliser (#category).
C'est cette définition qui justifie l'enrobage $(document).ready, afin
que le DOM soit prêt pour l'initialisation des templates.&lt;/p&gt;
&lt;p&gt;Autrement on peut toujours les initialiser dans le constructeur de la vue
mais cela impliquera un calcul à chaque instanciation d'objet, donc ce n'est
pas recommandé.&lt;/p&gt;
&lt;p&gt;Dans le constructeur nous allons nous assurer que le rendu de la vue
se fera toujours dans le contexte de cet objet (this).&lt;/p&gt;
&lt;p&gt;En effet le render pourra être appelé depuis d'autres endroits, notamment
le modèle.&lt;/p&gt;
&lt;p&gt;Dans ce cas nous voulons nous assurer que le this courant s'apparentera
toujours à cette vue. Nous utilisons pour cela bindAll.&lt;/p&gt;
&lt;p&gt;Enfin la méthode render va se servir du modèle que nous lui aurons passé pour générer notre LI.&lt;/p&gt;
&lt;p&gt;Plutôt que d'appeler $(@el) on utilise @.$el qui permet d'éviter d'accéder
à chaque fois au DOM en stockant une référence à l'élément.&lt;/p&gt;
&lt;p&gt;Render doit toujours retourner this afin de pouvoir chainer les appels.&lt;/p&gt;
&lt;p&gt;Un petit test en console:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// creation de la categorie&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Synbioz"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// creation de la vue&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CategoryView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// &amp;lt;li&amp;gt;Synbioz&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Regardons maintenant la vue qui listera l'ensemble des catégories:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CategoriesView&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;tagName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'ul'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'#categories'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bindAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'render'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'reset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@render&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;render&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;@template&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.categories'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;collection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@collection&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(category) -&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CategoryView&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;@&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Avec le binding du reset sur la collection nous introduisons toute la puissance
de Backbone.js. Ce code va permettre de surveiller la collection, et de faire
automatiquement un render de la vue associée.&lt;/p&gt;
&lt;p&gt;Dans la méthode render nous allons utiliser la vue précédente pour générer
chaque catégorie et l'ajouter à la liste.&lt;/p&gt;
&lt;p&gt;Plutôt que de rechercher .categories dans tout le DOM nous limitons
la portée au template, ce qui est nettement plus efficace.&lt;/p&gt;
&lt;h4&gt;Définition du routeur&lt;/h4&gt;
&lt;p&gt;Le routeur va nous permettre de définir les routes de notre application.
Il définit les routes en GET en faisant matcher l'URL courante sur une action.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MetricsRouter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'dashboard'&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;categories&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Categories&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@categoriesView&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CategoriesView&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#container"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;@categoriesView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="vi"&gt;@app.MetricsRouter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MetricsRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Dès que nous serons sur la home, la méthode dashboard sera appelée.
Dans celle ci on récupère les catégories, ce qui exécute directement
un rendering de la vue associée.&lt;/p&gt;
&lt;p&gt;Il ne reste qu'à l'ajouter dans le DOM.&lt;/p&gt;
&lt;h4&gt;Lancement de l'application&lt;/h4&gt;
&lt;p&gt;Maintenant que tout est en place, lançons l'application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;Backbone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nous n'avons pas encore de navigation mais cela n'en demeure
pas moins indispensable.&lt;/p&gt;
&lt;p&gt;Par défaut les URL sont gérées comme celles de twitter, avec un #.
Mais il existe une option (pushState) qui permet d'utiliser les possibilités d'HTML5
pour mettre à jour l'historique en javascript.&lt;/p&gt;
&lt;p&gt;Ainsi pour votre navigateur c'est complètement transparent, la navigation
s'apparente à une navigation classique.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Le rapport puissance / taille du code du Backbone.js est assez impressionnant.
Il permet de créer des applications plus fluides et réactives en limitant les
aller / retour avec le serveur.&lt;/p&gt;
&lt;p&gt;Ce cadre applicatif permet d'avoir du code bien organisé, qui plus est testable
avec jasmine.&lt;/p&gt;
&lt;p&gt;Dans la prochaine partie nous allons gérer toute la partie événementielle et
formulaire afin de pouvoir interagir avec la page et naviguer comme dans
une application classique.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Le méconnu inverse_of</title>
    <id>https://martin.catty.fr/blog/le-meconnu-inverse_of/</id>
    <link href="https://martin.catty.fr/blog/le-meconnu-inverse_of/" rel="alternate"/>
    <updated>2012-01-27T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Rails: le méconnu inverse_of&lt;/h2&gt;
&lt;h3&gt;Active record: le patron de conception&lt;/h3&gt;
&lt;p&gt;Active record, avant d'être le nom d'une célèbre gem inclue dans rails est
le nom d'un design pattern, permettant de faire du mapping objet - relationnel.&lt;/p&gt;
&lt;p&gt;C'est à dire qu'il vous permet de manipuler sous forme d'objets vos données
stockées dans une base de données.&lt;/p&gt;
&lt;h3&gt;Optimiser la communication entre les objets et la base de donnée&lt;/h3&gt;
&lt;p&gt;La gem ActiveRecord vous offre un maximum de confort en mettant à disposition
toutes les méthodes pour gérer les opérations basiques de CRUD mais aussi
les associations entre tables.&lt;/p&gt;
&lt;p&gt;Imaginons deux class product et category:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;has_many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:products&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;belongs_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Regardons quels sont les appels effectués à la base lorsque nous parcourons les objets:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n n-Operator"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n n-Operator"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"category_id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#&amp;lt;Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59"&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n n-Operator"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#&amp;lt;Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59"&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;010&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;ActiveRecord n'est pas suffisamment malin pour comprendre que nous sommes en fait sur la même
catégorie quand nous demandons la catégorie d'un produit qui fait parti de la liste des produits
de cette même catégorie.&lt;/p&gt;
&lt;h3&gt;Le rôle d'inverse_of&lt;/h3&gt;
&lt;p&gt;Redéfinissons nos relations pour utiliser inverse_of:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;has_many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:inverse_of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;belongs_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:inverse_of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:products&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;pre&gt;
  irb(main):004:0&gt; c2 = Category.first(:include =&gt; :products)
    Category Load (0.2ms)  SELECT "categories".* FROM "categories" LIMIT 1
    Product Load (0.2ms)  SELECT "products".* FROM "products" WHERE "products"."category_id" IN (1)
  =&gt; #&lt;Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59"&gt;
  irb(main):005:0&gt; c3 = c2.products.first.category
  =&gt; #&lt;Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59"&gt;
  irb(main):006:0&gt; c3.object_id == c2.object_id
  =&gt; true
&lt;/pre&gt;

&lt;p&gt;En explicitant la relation inverse ActiveRecord sait maintenant
retrouver ses petits et éviter d'inutiles requêtes SQL.&lt;/p&gt;
&lt;h3&gt;Les formulaires imbriqués&lt;/h3&gt;
&lt;p&gt;Imaginons que vous souhaitiez pouvoir créer directement une catégorie
avec un ou plusieurs produits et que vous produits doivent obligatoirement
avoir une catégorie.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;has_many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:inverse_of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;accepts_nested_attributes_for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:products&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;belongs_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:inverse_of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:products&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;validates_presence_of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Sans inverse_of vous risquez d'avoir des soucis à cause de la validation.
En effet, le produit ayant une validation sur sa catégorie, il va chercher à la charger.&lt;/p&gt;
&lt;p&gt;Sans inverse_of il essayera de la charger depuis la base de donnée, alors qu'elle n'existe
pas encore. Avec, il sera capable de récupérer directement l'objet.&lt;/p&gt;
&lt;h3&gt;Identity map&lt;/h3&gt;
&lt;p&gt;Très franchement ActiveRecord nous a habitué à un peu plus de magie et d'automatisme
pour que l'on n'ait pas envie de définir manuellement tous ses inverse_of.&lt;/p&gt;
&lt;p&gt;D'autant plus qu'il semble assez simple de retrouver les associations concordantes
et que cette fonctionnalité est présente dans &lt;a href="http://datamapper.org/why.html"&gt;datamapper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;C'est en fait le rôle de la fonction &lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/IdentityMap.html"&gt;d'identity map&lt;/a&gt; introduite par rails 3.1
mais désactivée par défaut.&lt;/p&gt;
&lt;p&gt;L'objectif est de n'avoir jamais à recharger un objet depuis la base quand il est déjà en mémoire.&lt;/p&gt;
&lt;p&gt;L'activation de la fonctionnalité se fait dans application.rb&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;005&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IdentityMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n n-Operator"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Loaded&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;From&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#&amp;lt;Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59"&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;L'utilisation se fait dans un bloc et l'usage est encore limité.
Par exemple utiliser Category.first, même dans le bloc entrainera
tout de même 2 requêtes.&lt;/p&gt;
&lt;p&gt;De plus l'activation de la fonction ne permet pas pour le moment
de remplacer le inverse_of.&lt;/p&gt;
&lt;p&gt;C'est pourtant une fonctionnalité très importante car elle
permettra de fortement &lt;a href="http://merbist.com/2010/07/29/object-allocation-why-you-should-care/"&gt;limiter le nombre d'instanciation d'objets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour autant elle n'est pas simple à mettre en place en conservant un comportement
tread-safe : on imagine bien deux morceaux de code entrain de mettre à jour le même objet en mémoire.&lt;/p&gt;
&lt;p&gt;En résumé, utilisez le inverse_of dès aujourd'hui si ce n'est pas encore le cas car Rails n'est
pas prêt de le faire automatiquement pour vous.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Jouons un peu avec les websockets</title>
    <id>https://martin.catty.fr/blog/jouons-un-peu-avec-les-websockets/</id>
    <link href="https://martin.catty.fr/blog/jouons-un-peu-avec-les-websockets/" rel="alternate"/>
    <updated>2011-12-20T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Jouons un peu avec les websockets&lt;/h2&gt;
&lt;p&gt;Dans notre &lt;a href="/blog/2011/08/16/introduction_a_html5"&gt;présentation d'html5&lt;/a&gt;, nous avons brièvement
évoqué les websockets.&lt;/p&gt;
&lt;p&gt;Les websockets sont un protocole de communication bi-directionnel, standardisé par le w3c et
qui est progressivement implémenté par les navigateurs.&lt;/p&gt;
&lt;p&gt;La promesse des websockets est d'offrir un mécanisme client / serveur dans votre navigateur.
À l'heure actuelle le principal mécanisme existant est HTTP, qui est un protocole sans état.&lt;/p&gt;
&lt;p&gt;Concrètement il n'a pas de possibilité de réutilisation de la connexion.
Une fois que le serveur a répondu, votre navigateur affiche la réponse mais le serveur n'a pas la possibilité de
dire «oups j'ai oublié de te dire quelquechose».&lt;/p&gt;
&lt;p&gt;Qui plus est la réponse est enrobée d'un tas de données complémentaires (headers…).
Ce n'est pas le cas pour les websockets.&lt;/p&gt;
&lt;h3&gt;Utiliser les websockets&lt;/h3&gt;
&lt;p&gt;La question qui brûle les lèvres est «peut on l'utiliser là tout de suite ?»&lt;/p&gt;
&lt;p&gt;&lt;a href="http://caniuse.com/#search=websockets"&gt;Oui&lt;/a&gt;, d'ailleurs nous le faisons. Coté navigateur, utiliser les websockets directement comporte
les même soucis que faire du js pur, c'est à dire qu'il y a un certain nombre d'implémentations.&lt;/p&gt;
&lt;p&gt;D'autre part la fonctionnalité a été désactivé dans Firefox 4 et 5 (puis réactivée), suite à la découverte d'une
faille de sécurité.&lt;/p&gt;
&lt;p&gt;Opera 11 l'a aussi désactivé pour cette raison tandis qu'Internet Explorer ne la supporte pas encore.&lt;/p&gt;
&lt;p&gt;Une nouvelle version corrige cette faille du protocole. Elle est implémentée par Firefox 6+ et Chrome 14+.&lt;/p&gt;
&lt;h4&gt;Pourquoi l'utiliser si ce n'est pas encore supporté partout ?&lt;/h4&gt;
&lt;p&gt;Comme pour HTML5 ou CSS3, la prise en charge va aller croissante, et tous les navigateurs vont être
amenés à l'implémenter (même IE 10).&lt;/p&gt;
&lt;p&gt;Et puis il existe aussi des outils capables de mettre en œuvre des mécanismes de substitutions quand la fonctionnalité
n'est pas présente ou activée.&lt;/p&gt;
&lt;h4&gt;socket.io&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://socket.io/"&gt;socket.io&lt;/a&gt; est un outil fantastique, capable d'utiliser plusieurs fallback si les websockets ne sont pas disponibles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;socket flash&lt;/li&gt;
&lt;li&gt;ajax&lt;/li&gt;
&lt;li&gt;iframe&lt;/li&gt;
&lt;li&gt;jsonp&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cela vous ouvre la porte à tous les navigateurs, même mobiles.&lt;/p&gt;
&lt;p&gt;L'utilisation est vraiment simple, on démarre une écoute côté serveur,
on récupère côté client le fichier socket.io.js livré par le serveur (grâce à une balise script),
et les deux parties sont prêtes à discuter.&lt;/p&gt;
&lt;p&gt;socket.io vous permet de faire facilement du broadcast ou de la gestion de channel.
Quant à la déconnexion, elle est prévue de base par le protocole.&lt;/p&gt;
&lt;h4&gt;Sécurité&lt;/h4&gt;
&lt;p&gt;On l'a vu plus tôt la spécification évolue pour prendre en compte la sécurité.&lt;/p&gt;
&lt;p&gt;Et comme http a https, ws a son pendant wss qui permet d'ouvrir une connexion sécurisée.&lt;/p&gt;
&lt;h3&gt;En route vers le temps réel&lt;/h3&gt;
&lt;p&gt;Les websockets ne sont pas comparables à AJAX, elles permettent l'ouverture d'un canal de communication entre le client et le serveur.
Du coup le serveur peut devenir une passerelle entre clients, par exemple pour réaliser un tchat.&lt;/p&gt;
&lt;p&gt;De même les weksockets peuvent permettre de communiquer entre serveurs, ce n'est pas une technologie utilisable uniquement par les navigateurs.&lt;/p&gt;
&lt;p&gt;De nombreuses implémentations existent dans différents langages, que ce soit PHP, Java ou encore ruby et javascript (node.js).&lt;/p&gt;
&lt;p&gt;Il existe même un framework bâti avec ruby, &lt;a href="http://cramp.in/"&gt;cramp&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Démo&lt;/h3&gt;
&lt;p&gt;Plutôt que de vous ennuyer avec un tchat j'ai écrit un puissance 4, afin que 2 joueurs puissent s'affronter.&lt;/p&gt;
&lt;p&gt;Le code est disponible sur &lt;a href="https://github.com/synbioz/puissance4"&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;iframe src="http://player.vimeo.com/video/33908381?title=0&amp;amp;byline=0&amp;amp;portrait=0" width="500" height="313" frameborder="0" webkitAllowFullScreen="true" mozallowfullscreen="true" allowFullScreen="true"&gt; &lt;/iframe&gt;

&lt;p&gt;&lt;a href="http://vimeo.com/33908381"&gt;Puissance 4&lt;/a&gt; from &lt;a href="http://vimeo.com/synbioz"&gt;synbioz&lt;/a&gt; on &lt;a href="http://vimeo.com"&gt;Vimeo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;L'essentiel du code est dans src, et il n'est pas énorme, environ 160 LOC de &lt;a href="http://www.synbioz.com/blog/2011/08/02/introduction_a_coffeescript"&gt;coffee&lt;/a&gt; pour le serveur et une 100aine pour le client.&lt;/p&gt;
&lt;h3&gt;À propos du code&lt;/h3&gt;
&lt;p&gt;La présence d'un Gemfile est simplement liée au fait que j'utilise un guardfile
pour compiler mon coffeescript en javascript.&lt;/p&gt;
&lt;p&gt;La couche serveur est gérée en node.js, la page web est statique.&lt;/p&gt;
&lt;p&gt;Pour faire tourner le jeu vous avez uniquement besoin de node, npm et du module
socket.io&lt;/p&gt;
&lt;p&gt;Le jeu est en fait représenté par une classe game. Le client n'a pas de
référence sur le jeu, toute la logique du jeu est gérée par le serveur.&lt;/p&gt;
&lt;p&gt;Le serveur est en attente de connexion. Une fois qu'un client se connecte il
vérifie qu'un jeu est disponible et le signifie au client si c'est le cas en lui
indiquant s'il est le joueur 1 ou 2.&lt;/p&gt;
&lt;p&gt;Si aucun jeu n'est démarré et que la limite (2 actuellement) n'est pas atteinte,
un jeu est créé et la matrice initialisée.&lt;/p&gt;
&lt;p&gt;Une fois que les 2 utilisateurs sont enregistrés on envoie aux 2 joueurs un
message indiquant que le jeu démarre.&lt;/p&gt;
&lt;p&gt;Le premier inscrit commence. À chaque coup joué on vérifie s'il est gagnant ou
non, plutôt que de parcourir la grille entière, et on change de joueur.&lt;/p&gt;
&lt;p&gt;Si le coup est gagnant on notifie le gagnant et le perdant, et on réactive le bouton
permettant de lancer un jeu tout en supprimant la référence sur le serveur.&lt;/p&gt;
&lt;p&gt;Le jeu est simplement un exemple de la puissance des websockets. Il ne vise pas à
implémenter complètement la logique du jeu, mais si l'envie vous en prend n'hésitez
à proposer une pull request.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Déployer et héberger une application Ruby on Rails avec Capistrano Unicorn et Nginx</title>
    <id>https://martin.catty.fr/blog/deployer-et-heberger-une-application-ruby-on-rails-avec-capistrano-unicorn-et-nginx/</id>
    <link href="https://martin.catty.fr/blog/deployer-et-heberger-une-application-ruby-on-rails-avec-capistrano-unicorn-et-nginx/" rel="alternate"/>
    <updated>2011-11-15T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Déployer et héberger une application Ruby on Rails avec Capistrano, Unicorn et Nginx&lt;/h2&gt;
&lt;h3&gt;Mettre en place Capistrano en multistage&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/capistrano/capistrano"&gt;Capistrano&lt;/a&gt; est un outil écrit en Ruby qui permet de faciliter les déploiements d'application.
Il n'est pas obligatoire que l'application soit une application Rails, cela fonctionne aussi
avec des applications sinatra et même des applications non Ruby (Node.js…)&lt;/p&gt;
&lt;p&gt;L'objectif du multistaging est de pouvoir déployer différement selon les environnements (staging, production…)&lt;/p&gt;
&lt;p&gt;Pour commencer nous allons ajouter les gems nécessaires  à notre projet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'capistrano'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'capistrano-ext'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Une fois les gems enregistrés nous allons les installer et mettre en place Capistrano :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;bundle&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--binstubs
$&lt;span class="w"&gt; &lt;/span&gt;bin/capify&lt;span class="w"&gt; &lt;/span&gt;.
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;config/deploy
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;config/environments/production.rb&lt;span class="w"&gt; &lt;/span&gt;config/environments/staging.rb
$&lt;span class="w"&gt; &lt;/span&gt;cap&lt;span class="w"&gt; &lt;/span&gt;staging&lt;span class="w"&gt; &lt;/span&gt;deploy:setup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Le binstubs permet de freezer les bins, &lt;a href="http://yehudakatz.com/2011/05/30/gem-versioning-and-bundler-doing-it-right/"&gt;c'est une bonne pratique&lt;/a&gt;.
Cela permet de lancer des commandes comme bin/rake plutôt que bundle exec rake.&lt;/p&gt;
&lt;p&gt;Le capify va créer le fichier config/deploy.rb que nous allons éditer par la suite.&lt;/p&gt;
&lt;p&gt;La création du dossier config/deploy va permettre de mettre en place une configuration
de déploiement propre à chaque environnement (ex: config/deploy/production.rb).&lt;/p&gt;
&lt;p&gt;Toutefois l'essentiel de la configuration se fera dans config/deploy.rb car elle varie
peu d'un environnement à l'autre.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bundler/capistrano"&lt;/span&gt;

&lt;span class="c1"&gt;# allowing shell interactions&lt;/span&gt;
&lt;span class="n"&gt;default_run_options&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:pty&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# multistaging&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:stages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;%w(staging production)&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:default_stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'capistrano/ext/multistage'&lt;/span&gt;

&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fake"&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synbioz"&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"ssh://user@server/git/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git"&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:ssh_options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:forward_agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:deploy_to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/www/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:scm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:git&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:deploy_via&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:remote_cache&lt;/span&gt;

&lt;span class="c1"&gt;# number of releases we want to keep&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:keep_releases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:use_sudo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="c1"&gt;# default rails_env, should be overrided in config/deploy/#{environnement}.rb&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:rails_env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;

&lt;span class="c1"&gt;# Unicorn informations&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:unicorn_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/unicorn.rb"&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:unicorn_pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pids/unicorn.pid"&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:unicorn_bin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/unicorn"&lt;/span&gt;

&lt;span class="c1"&gt;# useful for rbenv&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:default_environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;'PATH'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/synbioz/.rbenv/shims:/home/synbioz/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:maintenance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ln -nfs &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/system/_maintenance.html &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/system/maintenance.html"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rm -f &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/system/maintenance.html"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:except&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:no_release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="dl"&gt;CMD&lt;/span&gt;
&lt;span class="sh"&gt;      cd #{current_path} &amp;amp;&amp;amp; #{unicorn_bin} -c #{unicorn_config} -E #{rails_env} -D&lt;/span&gt;
&lt;span class="dl"&gt;    CMD&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:force_stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:except&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:no_release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="dl"&gt;CMD&lt;/span&gt;
&lt;span class="sh"&gt;      if [ -e #{unicorn_pid} ]; then&lt;/span&gt;
&lt;span class="sh"&gt;        kill -9 $(cat #{unicorn_pid});&lt;/span&gt;
&lt;span class="sh"&gt;      fi&lt;/span&gt;
&lt;span class="dl"&gt;    CMD&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:except&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:no_release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="dl"&gt;CMD&lt;/span&gt;
&lt;span class="sh"&gt;      if [ -e #{unicorn_pid} ]; then&lt;/span&gt;
&lt;span class="sh"&gt;        kill $(cat #{unicorn_pid});&lt;/span&gt;
&lt;span class="sh"&gt;      fi&lt;/span&gt;
&lt;span class="dl"&gt;    CMD&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:graceful_stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:except&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:no_release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="dl"&gt;CMD&lt;/span&gt;
&lt;span class="sh"&gt;      if [ -e #{unicorn_pid} ]; then&lt;/span&gt;
&lt;span class="sh"&gt;        kill -s QUIT $(cat #{unicorn_pid});&lt;/span&gt;
&lt;span class="sh"&gt;      fi&lt;/span&gt;
&lt;span class="dl"&gt;    CMD&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:reload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:except&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:no_release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="dl"&gt;CMD&lt;/span&gt;
&lt;span class="sh"&gt;      if [ -e #{unicorn_pid} ]; then&lt;/span&gt;
&lt;span class="sh"&gt;        kill -s USR2 $(cat #{unicorn_pid});&lt;/span&gt;
&lt;span class="sh"&gt;      fi&lt;/span&gt;
&lt;span class="dl"&gt;    CMD&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:restart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:except&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:no_release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="dl"&gt;CMD&lt;/span&gt;
&lt;span class="sh"&gt;      if [ -e #{unicorn_pid} ]; then&lt;/span&gt;
&lt;span class="sh"&gt;        kill -9 $(cat #{unicorn_pid});&lt;/span&gt;
&lt;span class="sh"&gt;        sleep 5;&lt;/span&gt;
&lt;span class="sh"&gt;        cd #{current_path} &amp;amp;&amp;amp; #{unicorn_bin} -c #{unicorn_config} -E #{rails_env} -D;&lt;/span&gt;
&lt;span class="sh"&gt;      fi&lt;/span&gt;
&lt;span class="dl"&gt;    CMD&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create shared folders."&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'deploy:setup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# for unicorn&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mkdir -p &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sockets"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mkdir -p &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# only for sqlite&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mkdir -p &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/db"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Link db.yml."&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'deploy:update_code'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ln -nfs &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/database.yml &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;release_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/database.yml"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Seed datas when cold deploying."&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'deploy:migrate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:no_release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;release_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; bin/rake db:seed RAILS_ENV=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;rails_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Link maintenance, rbenv and precompile assets."&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'deploy:symlink'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ln -nfs &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/.rbenv-version &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;release_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/.rbenv-version"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cp &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;release_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/public/_maintenance.html &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;shared_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/system/"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;release_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; RAILS_ENV=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;rails_env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bin/rake assets:precompile"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remove unused releases."&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deploy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deploy:cleanup"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Le lancement de deploy:setup va créer les dossiers nécessaires sur le serveur.&lt;/p&gt;
&lt;p&gt;Une fois réalisé, il faudra mettre en place une fois pour toute les fichiers de configuration
nécessaires sur le serveur, à savoir shared/config/database.yml et shared/config/.rbenv-version&lt;/p&gt;
&lt;p&gt;Ceux ci seront liés dans chaque release.&lt;/p&gt;
&lt;p&gt;Nous pouvons maintenant lancer le premier déploiement (cold):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;staging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;cold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Certaines tâches ne s'effectuent qu'au deploy:cold, dans notre exemple le db:seed.
Pour cela nous avons utilisé le paramètre :only =&amp;gt; { :no_release =&amp;gt; true }.&lt;/p&gt;
&lt;p&gt;A l'issu du deploy:cold si tout s'est bien passé la tâche start sera lancée et Unicorn démarré.&lt;/p&gt;
&lt;p&gt;Pour les prochains déploiements il suffira de déployer via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;staging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Configurer Unicorn&lt;/h3&gt;
&lt;p&gt;La force d'Unicorn est d'avoir un processus master qui s'occupe de charger l'application et
de démarrer des workers qui pourront traiter les requêtes.&lt;/p&gt;
&lt;p&gt;Chaque worker n'a donc pas à charger l'application Rails, ce qui entraine un important gain de temps.
Si un worker plante, le master s'occupera de le relancer.&lt;/p&gt;
&lt;p&gt;De plus Unicorn offre la promesse d'une application 0 downtime.
En effet l'envoi d'un signal USR2 au master lui indique de recharger l'application Rails (voir tâche reload dans deploy.rb).&lt;/p&gt;
&lt;p&gt;A noter toutefois que cette technique n'est pas fiable à 100%. Il m'est
arrivé que l'application ne soit pas rechargée. J'ai donc ajouté une tâche
restart qui s'assurer d'arrêter puis démarrer l'application.&lt;/p&gt;
&lt;p&gt;La configuration d'Unicorn se fait dans config/unicorn.rb
Si la configuration d'Unicorn est différente entre le staging et la production
on pourrait utiliser un sous dossier comme pour Capistrano.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# unicorn_rails -c config/unicorn.rb -E production -D&lt;/span&gt;
&lt;span class="n"&gt;worker_processes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synbioz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"users"&lt;/span&gt;
&lt;span class="n"&gt;working_directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/www/fake/current"&lt;/span&gt;

&lt;span class="n"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/www/fake/shared/sockets/unicorn.sock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:backlog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;
&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/www/fake/shared/pids/unicorn.pid"&lt;/span&gt;

&lt;span class="c1"&gt;# nuke workers after 30 seconds instead of 60 seconds (the default)&lt;/span&gt;
&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="n"&gt;stderr_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/www/fake/shared/log/unicorn.stderr.log"&lt;/span&gt;
&lt;span class="n"&gt;stdout_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/www/fake/shared/log/unicorn.stdout.log"&lt;/span&gt;

&lt;span class="c1"&gt;# combine REE with "preload_app true" for memory savings&lt;/span&gt;
&lt;span class="c1"&gt;# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow&lt;/span&gt;
&lt;span class="n"&gt;preload_app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:copy_on_write_friendly&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_on_write_friendly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="n"&gt;before_fork&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;defined?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect!&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;old_pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:pid&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.oldbin"&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;old_pid&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker_processes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:QUIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:TTOU&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ENOENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ESRCH&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;after_fork&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;defined?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;establish_connection&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Configurer Nginx&lt;/h3&gt;
&lt;p&gt;La configuration de notre vhost sous Nginx est très simple:&lt;/p&gt;
&lt;pre&gt;
server {
  listen 80;
  server_name fake.server;

  access_log /var/log/nginx/fake.access.log;
  error_log /var/log/nginx/fake.error.log;

  # direct to maintenance if this file exists
  if (-f $document_root/system/maintenance.html) {
    rewrite  ^(.*)$  /system/maintenance.html last;
    break;
  }

  location / {
    root /www/fake/current/public;

    proxy_redirect          http://fake/               /;
    proxy_set_header        Host                                                            $host;
    proxy_set_header        X-Real-IP                                                     $remote_addr;
    proxy_set_header  X-Forwarded-For                                               $proxy_add_x_forwarded_for;

    # If the file exists as a static file serve it directly
    if (-f $request_filename) {
      break;
    }

    if (!-f $request_filename) {
      proxy_pass http://fake;
      break;
    }
  }

  error_page   500 502 503 504  /500.html;
  location = /500.html {
    root   /www/fake/current/public;
  }
}
&lt;/pre&gt;

&lt;p&gt;Dans le fichier nginx.conf il suffit de référencer la socket Unicorn :&lt;/p&gt;
&lt;pre&gt;
upstream fake {
  server unix:/www/fake/shared/sockets/unicorn.sock;
}
&lt;/pre&gt;

&lt;p&gt;La seule subtilité vient du rewrite en cas de présence du fichier de maintenance.
Cela permet de mettre en place une page qui interceptera toutes les requêtes.&lt;/p&gt;
&lt;p&gt;Cette page peut être mise en place très simplement avec notre commande Capistrano :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cap&lt;span class="w"&gt; &lt;/span&gt;staging&lt;span class="w"&gt; &lt;/span&gt;maintenance:start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;En espérant que cet article facilite vos déploiements à venir.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Tester une propriété CSS via javascript</title>
    <id>https://martin.catty.fr/blog/tester-une-propriete-css-via-javascript/</id>
    <link href="https://martin.catty.fr/blog/tester-une-propriete-css-via-javascript/" rel="alternate"/>
    <updated>2011-11-02T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;L'approche classique&lt;/h2&gt;
&lt;p&gt;Dans une logique d'amélioration progressive, il est important de savoir ce que l'on
peut faire ou ne pas faire en CSS avec le navigateur de l'internaute.&lt;/p&gt;
&lt;p&gt;Par exemple si l'on souhaite vérifier que le navigateur supporte l'opacité:&lt;/p&gt;
&lt;pre&gt;
if('opacity' in document.body.style) {
  // C'est parti…
}
&lt;/pre&gt;

&lt;h2&gt;L'approche media queries&lt;/h2&gt;
&lt;p&gt;Les &lt;a href="http://www.w3.org/TR/css3-mediaqueries/"&gt;media queries&lt;/a&gt; permettent d'adapter ses feuilles
de style selon le media ciblé et ses capacités.&lt;/p&gt;
&lt;p&gt;Cette approche est utilisée dans la &lt;a href="http://www.synbioz.com/realisations"&gt;partie réalisations du site de Synbioz&lt;/a&gt;.
Si nous sommes en présence d'un navigateur supportant -webkit-transform-3d, on peut mettre en place l'effet cube.&lt;/p&gt;
&lt;p&gt;Ici impossible d'utiliser l'approche classique car javascript connait webkitTransform mais pas webkitTransform3D.&lt;/p&gt;
&lt;p&gt;Afin de savoir dans mon code javascript si cette propriété est supportée nous avons donc utilisé la puissance des media
queries, avec un morceau de code très léger:&lt;/p&gt;
&lt;pre&gt;
#cube {
  height: 100px;
}

@media (-webkit-transform-3d) {
  height: 200px;
}
&lt;/pre&gt;

&lt;p&gt;Au niveau du javascript il reste à tester la hauteur de l'élément en question. Généralement on utilisera un seul élément
caché dans le &lt;acronym title="Document Object Model"&gt;DOM&lt;/acronym&gt; pour tester toutes les propriétés dont on a besoin.&lt;/p&gt;
&lt;pre&gt;
var cube = document.getElementById("cube");
if(200 == cube.offsetHeight) {
  // propriété supportée
}
&lt;/pre&gt;

&lt;p&gt;Cet article évoque brièvement les media queries dans un usage un peu détourné mais cette nouveauté de CSS3 mériterait un
article à elle seule, tant elle peut se révéler pratique.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Proposer une liste de valeurs avec l'i18n de rails</title>
    <id>https://martin.catty.fr/blog/proposer-une-liste-de-valeurs-avec-li18n-de-rails/</id>
    <link href="https://martin.catty.fr/blog/proposer-une-liste-de-valeurs-avec-li18n-de-rails/" rel="alternate"/>
    <updated>2011-10-19T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Proposer plusieurs options dans un select avec l'i18n&lt;/h2&gt;
&lt;p&gt;Bien souvent dans une application vous avez besoin de proposer une liste
de valeurs plus ou moins fixes parmi lesquelles l'utilisateur doit faire un choix,
par exemple pour choisir sa civilité.&lt;/p&gt;
&lt;p&gt;Plutôt que d'utiliser une constante pour stocker un hash clé / valeur des options,
ou maintenir un système autre, j'utilise l'i18n de rails.&lt;/p&gt;
&lt;p&gt;Cela offre une solution très souple, voyez plutôt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# fr.yml
gender:
  miss: "Mademoiselle"
  mrs: "Madame"
  mr: "Monsieur"

# code avec formtastic
f.input :gender, :as =&amp;gt; :select, :collection =&amp;gt; I18n.t("gender").map(&amp;amp;:reverse)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;J'ai parlé d'utiliser des valeurs fixes mais vous pouvez très bien ajouter
progressivement des valeurs dans votre fichier d'i18n qui seront
automatiquement disponibles dans vos vues.&lt;/p&gt;
&lt;p&gt;C'est bien entendu encore plus avantageux quand vous travaillez sur une application multilingue.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Intégrer les réseaux sociaux dans son application ruby on rails avec OAuth</title>
    <id>https://martin.catty.fr/blog/integrer-les-reseaux-sociaux-dans-son-application-ruby-on-rails-avec-oauth/</id>
    <link href="https://martin.catty.fr/blog/integrer-les-reseaux-sociaux-dans-son-application-ruby-on-rails-avec-oauth/" rel="alternate"/>
    <updated>2011-10-18T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Intégrer les réseaux sociaux dans son application ruby on rails avec omniauth&lt;/h2&gt;
&lt;p&gt;Dans mon &lt;a href="http://www.synbioz.com/blog/2011/08/30/presentation_de_oauth"&gt;introduction à OAuth&lt;/a&gt;,
je vous avez laissé sur votre faim côté implémentation car ce n'était pas l'objet du billet.&lt;/p&gt;
&lt;p&gt;J'avais juste brièvement évoqué &lt;a href="https://github.com/intridea/omniauth"&gt;omniauth&lt;/a&gt; comme l'outil indispensable.&lt;/p&gt;
&lt;p&gt;Voyons donc aujourd'hui comment réellement intégrer cette solution avec &lt;a href="https://github.com/plataformatec/devise"&gt;devise&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Créer des applications sur les plateformes ciblées&lt;/h3&gt;
&lt;p&gt;Qu'elle que soit la plateforme en question (google, twitter, viadeo, facebook, linkedin…) vous aurez besoin de créer
une application sur la plateforme cible. Par exemple sur twitter: &lt;a href="https://dev.twitter.com/apps/new"&gt;https://dev.twitter.com/apps/new&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ce qui nous intéresse c'est de récupérer les clés qui nous permettront d'interagir avec l'application,
à savoir le consumer key et le consumer secret.&lt;/p&gt;
&lt;h3&gt;Configurer notre application rails&lt;/h3&gt;
&lt;p&gt;En premier lieu, commençons par le plus simple, à savoir installer omniauth. Il suffit de l'ajouter dans le
Gemfile.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'omniauth'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Pour utiliser openid (pour s'authentifier via google par exemple), nous aurons besoin des gems suivantes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'oa-core'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'oa-openid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'omniauth/openid'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Configurer devise&lt;/h3&gt;
&lt;p&gt;Omniauth s'intègre très bien avec devise, il suffit de configurer votre initializer devise.rb.
Il est de bon ton d'avoir des applications pour votre environnement de dev et d'autres pour votre production.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;development?&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:twitter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_SECRET'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:facebook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_SECRET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:linked_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_SECRET'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:viadeo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_SECRET'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:google_apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;OpenID&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Store&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Filesystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'gmail.com'&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:twitter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_SECRET'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:facebook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_SECRET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:linked_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_SECRET'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:viadeo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'CONSUMER_SECRET'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;omniauth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:google_apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;OpenID&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Store&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Filesystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'gmail.com'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Configurer notre modèle&lt;/h3&gt;
&lt;p&gt;Dans le modèle qui gère vos utilisateurs il faut rajouter omniauthable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# user.rb&lt;/span&gt;
&lt;span class="n"&gt;devise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:omniauthable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Configurer nos routes et notre contrôleur&lt;/h3&gt;
&lt;p&gt;Nous allons indiquer à devise quel contrôleur utiliser lors de la réception des callbacks
des différentes applications…&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;devise_for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:controllers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:omniauth_callbacks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"users/omniauth_callbacks"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;…et créer le contrôleur correspondant:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;omniauth_callbacks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notre contrôleur doit hériter de Devise::OmniauthCallbacksController et implémenter une action
par service supporté.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OmniauthCallbacksController&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OmniauthCallbacksController&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;linked_in&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# some stuff&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;twitter&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Dès lors que vous avez configuré un service, le formulaire de login de devise vous offre
immédiatement un lien pour se connecter avec ce service.&lt;/p&gt;
&lt;p&gt;Quand vous suivrez ce lien depuis votre application, la plateforme cible (ex: twitter) vous demandera
d'autoriser explicitement l'utilisation de vos informations.&lt;/p&gt;
&lt;p&gt;Si vous acceptez vous serez redirigé vers l'application que vous développez. A la redirection
un hash (env["omniauth.auth"]) est transmis. C'est là que vous trouverez toutes les informations
de l'utilisateur selon la plateforme (email, nom, prénom…)&lt;/p&gt;
&lt;h3&gt;Gestion des autorisations&lt;/h3&gt;
&lt;p&gt;Beaucoup d'applications prennent la liberté de créer des comptes utilisateurs à la volée au retour
de ces informations, en générant un mot de passe, voire un email, aléatoire.&lt;/p&gt;
&lt;p&gt;Je pense personnellement que c'est une mauvaise idée. En faisant cela l'utilisateur ne comprendra
pas qu'il vient de créer un compte sur votre application, donc il risque fort de créer 1 compte par
plateforme utilisée.&lt;/p&gt;
&lt;p&gt;De votre côté vous allez vous retrouver avec des données inconsistantes.&lt;/p&gt;
&lt;h4&gt;Créer un modèle authorization&lt;/h4&gt;
&lt;p&gt;Nous allons créer un modèle authorization qui permettra de stocker les informations propres
à chaque service permettant de s'authentifier.&lt;/p&gt;
&lt;p&gt;Cette autorisation est rattachée à un utilisateur, les champs indispensables sont donc:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Authorization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;references&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="c1"&gt;# user.rb&lt;/span&gt;
&lt;span class="n"&gt;has_many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:authorizations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:dependent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:destroy&lt;/span&gt;
&lt;span class="c1"&gt;# authorization.rb&lt;/span&gt;
&lt;span class="n"&gt;belongs_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4&gt;Utilisateur connecté&lt;/h4&gt;
&lt;p&gt;Si vous êtes connecté, vous avez un current_user donc vous pouvez créer une authorization depuis votre contrôleur.&lt;/p&gt;
&lt;p&gt;Depuis l'espace où vous êtes connecté dans votre application vous pouvez lister les autorisations présentes et permettre à l'utilisateur
de les révoquer.&lt;/p&gt;
&lt;h4&gt;Utilisateur déconnecté&lt;/h4&gt;
&lt;p&gt;Si vous n'êtes pas connecté, la première chose à faire est d'essayer de voir si une autorisation correspondante
à l'uid récupéré dans le hash existe depuis le contrôleur de callback.&lt;/p&gt;
&lt;p&gt;Si c'est le cas on récupère l'user rattaché à cette autorisation et il devient à la fois authentifié et notre current_user.&lt;/p&gt;
&lt;p&gt;S'il n'y a pas d'autorisation correspondante c'est certainement que l'utilisateur veut se créer un compte.&lt;/p&gt;
&lt;p&gt;Dans ce cas on va simplement récupérer les informations, les utiliser pour construire notre utilisateur et
afficher la vue d'inscription.&lt;/p&gt;
&lt;p&gt;Restera alors uniquement à l'utilisateur de rentrer son mot de passe et son email si celui ci n'est pas fourni, ce
qui est un confort non négligeable.&lt;/p&gt;
&lt;h3&gt;Problèmes rencontrés&lt;/h3&gt;
&lt;p&gt;L'intégration de google m'a posé quelques problèmes, lorsque j'étais authentifié et que je tentais d'autoriser
google, au retour mon current_user était nil, la session avait été remise à 0.&lt;/p&gt;
&lt;p&gt;En fouillant un peu je me suis rendu compte qu'&lt;a href="https://github.com/intridea/omniauth/issues/203"&gt;un ticket avait été ouvert&lt;/a&gt;.
Cela vient apparemment du formulaire d'envoi des paramètres à google qui ne contient pas le token de protection.&lt;/p&gt;
&lt;p&gt;Ce token n'étant pas envoyé, rails fait un reset de la session.&lt;/p&gt;
&lt;p&gt;Il y a deux solutions, surcharger le formulaire existant de omniauth pour google en utilisant le helper form_tag qui inclue tout seul
le token, ou dans notre contrôleur de callback contourner la vérification:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;protect_from_forgery&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:except&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:google_apps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Documentation complémentaire&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview"&gt;La doc de devise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/993566"&gt;Ce gist&lt;/a&gt; est un bon point de départ, mis à part qu'il crée des comptes à la volée,
et que la quasi totalité du code privé du contrôleur devrait être déporté dans le modèle authorization.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>Accélérer le mode développement avec Rails 3.1</title>
    <id>https://martin.catty.fr/blog/accelerer-le-mode-developpement-avec-rails-3.1/</id>
    <link href="https://martin.catty.fr/blog/accelerer-le-mode-developpement-avec-rails-3.1/" rel="alternate"/>
    <updated>2011-10-07T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Accélérer le mode développement avec Rails 3.1&lt;/h2&gt;
&lt;p&gt;L'asset pipeline en Rails 3.1 fait couler beaucoup d'encre
numérique, et pas toujours en bien.&lt;/p&gt;
&lt;p&gt;De mon côté, sur une application 3.1 fraîche, j'ai commencé
à voir les temps d'affichage s'accroitrent de plus en plus,
avant de me rendre compte en console qu'il s'agissait
essentiellement d'un problème de chargement des assets.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/pipeline.png"&gt;&lt;img alt="Pipeline avant" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/pipeline_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;+8 secondes&lt;/strong&gt; pour afficher une image ce n'est clairement pas normal.&lt;/p&gt;
&lt;p&gt;Le problème c'est que sprocket est utilisé comme une application rack classique.&lt;/p&gt;
&lt;p&gt;Il est monté dans rails et chaque processing d'asset
en développement entraîne un rechargement complet de la stack.&lt;/p&gt;
&lt;p&gt;Pour (entre autre) désactiver ce comportement je vous conseille l'installation de la gem &lt;a href="https://github.com/wavii/rails-dev-tweaks"&gt;rails-dev-tweak&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Celle ci va se charger d'intercepter les requêtes concernant les assets et court-circuiter sprocket.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/pipeline-after.png"&gt;&lt;img alt="Pipeline après" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/pipeline-after_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Synbioz recrute un développeur ruby on rails</title>
    <id>https://martin.catty.fr/blog/synbioz-recrute-un-developpeur-ruby-on-rails/</id>
    <link href="https://martin.catty.fr/blog/synbioz-recrute-un-developpeur-ruby-on-rails/" rel="alternate"/>
    <updated>2011-10-06T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Synbioz recrute un développeur ruby on rails&lt;/h2&gt;
&lt;h3&gt;Société&lt;/h3&gt;
&lt;p&gt;Depuis 2007, &lt;a href="http://www.synbioz.com"&gt;Synbioz&lt;/a&gt; est spécialisée dans le développement d'applications web et métier en ruby on rails.&lt;/p&gt;
&lt;p&gt;Nous accompagnons des sociétés aux métiers aussi divers que passionnants, chaque projet est unique, sur mesure et centré utilisateur.&lt;/p&gt;
&lt;p&gt;Pour poursuivre notre croissance nous recherchons un développeur Ruby on Rails.&lt;/p&gt;
&lt;h3&gt;Description du poste&lt;/h3&gt;
&lt;p&gt;Intégré(e) à une &lt;a href="http://www.synbioz.com/equipe"&gt;équipe expérimentée&lt;/a&gt; et passionné(e) de Ruby on Rails, vous participez à la conception et au développement d'application web en contact direct avec nos clients.&lt;/p&gt;
&lt;p&gt;Vous travaillerez dans un cadre technique stimulant et selon les méthodologies agiles. Nous recherchons avant tout un développeur passionné, autonome et ayant l'esprit d'équipe et d'initiative.&lt;/p&gt;
&lt;h3&gt;Formation&lt;/h3&gt;
&lt;p&gt;Autodidacte, bac +2 à bac +5.&lt;/p&gt;
&lt;h3&gt;Expérience&lt;/h3&gt;
&lt;p&gt;Vous avez de bonnes connaissances en développement web et au moins une première approche de Rails.&lt;/p&gt;
&lt;p&gt;Toutes autres expériences complémentaires seront appréciées, notamment la participation à des projets libres et le développement mobile.&lt;/p&gt;
&lt;h3&gt;Votre environnement&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pas de windows&lt;/li&gt;
&lt;li&gt;git, os x || linux&lt;/li&gt;
&lt;li&gt;Télétravail&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Nos outils&lt;/h3&gt;
&lt;p&gt;Votre trousse à outil chez Synbioz se constituera des éléments suivants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rails&lt;/li&gt;
&lt;li&gt;rspec&lt;/li&gt;
&lt;li&gt;git&lt;/li&gt;
&lt;li&gt;node.js&lt;/li&gt;
&lt;li&gt;socket.io&lt;/li&gt;
&lt;li&gt;jquery&lt;/li&gt;
&lt;li&gt;prototype&lt;/li&gt;
&lt;li&gt;mongodb&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nous ne vous demandons pas de maitriser ces outils pour postuler.&lt;/p&gt;
&lt;p&gt;Il s'agit justement d'outils que vous serez amenés à utiliser et que nous vous aiderons
à prendre en main.&lt;/p&gt;
&lt;h3&gt;Rémunération&lt;/h3&gt;
&lt;p&gt;Selon formation et expérience.&lt;/p&gt;
&lt;h3&gt;Equipe&lt;/h3&gt;
&lt;p&gt;Notre équipe actuelle se compose de 3 profils techniques et d'un commercial.
Le siège est basé à Paris mais l'ensemble de l'équipe se situe autour de Lille.&lt;/p&gt;
&lt;p&gt;Le poste est ouvert partout en France de part le télétravail mais à compétences
égales la proximité sera privilégiée.&lt;/p&gt;
&lt;h3&gt;Postuler&lt;/h3&gt;
&lt;p&gt;Vous pouvez &lt;a href="http://www.synbioz.com/contact"&gt;nous contacter directement via le site&lt;/a&gt;,
ou à l'adresse &lt;a href="mailto:job@synbioz.com"&gt;job@synbioz.com&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;En savoir plus&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.synbioz.com"&gt;Notre site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.synbioz.com/blog"&gt;Notre blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.synbioz.com/portfolio"&gt;Notre portfolio&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
  <entry>
    <title>Présentation de OAuth</title>
    <id>https://martin.catty.fr/blog/presentation-de-oauth/</id>
    <link href="https://martin.catty.fr/blog/presentation-de-oauth/" rel="alternate"/>
    <updated>2011-08-30T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Qu'est ce que OAuth ?&lt;/h2&gt;
&lt;p&gt;OAuth est né de la volonté d'offrir une solution simple et sécurisée aux mécanismes d'autorisation 3 parties.
D'une part le client qui représente l'application qui veut utiliser mes données, d'autre part
le serveur qui possède les données en question et enfin l'utilisateur qui valide ou non l'accès à ses données.&lt;/p&gt;
&lt;p&gt;En effet les différents services web sont de plus en plus interconnectés, par le biais d'API, et il n'est pas rare
qu'en tant qu'utilisateur je veuille donner la permission à l'application X d'utiliser certaines de mes données de
l'application Y.&lt;/p&gt;
&lt;p&gt;Avant OAuth, pour y parvenir, je n'avais pas beaucoup de solutions. J'étais généralement obligé de livrer mon login /
mot de passe de Y à X.&lt;/p&gt;
&lt;h2&gt;Les usages concrets&lt;/h2&gt;
&lt;p&gt;Ils sont partout, souvent sans que l'on s'en rende compte. Par exemple lorsque j'utilise un client twitter
sur smartphone ou sur ordinateur, à chaque fois que je veux poster un message l'application ne me demande
pas mon mot de passe.&lt;/p&gt;
&lt;p&gt;L'application en question a simplement eu besoin que je l'autorise à un moment donné, mes identifiants
ne sont pas transmis à chaque requête.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/twitter.png"&gt;&lt;img alt="Accès twitter" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/twitter_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Comme le montre la capture, je peux à tout moment révoquer cet accès.&lt;/p&gt;
&lt;h2&gt;Le fonctionnement&lt;/h2&gt;
&lt;p&gt;Le graphique de Yahoo explique bien le cheminement nécessaire pour qu'un utilisateur puisse donner accès à une ressource.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/oauth_graph.gif"&gt;&lt;img alt="OAuth graph" src="https://synbioz-website-attachments.s3.eu-west-1.amazonaws.com/articles/oauth_graph_thumb_450.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Voici le déroulement du processus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le client et le serveur doivent se mettent d'accord dans un premier temps. Pour cela des jetons clients
doivent être créés. Par exemple sur twitter quand vous créez une application, vous récupérez les jetons clients (consumer key et consumer secret).&lt;/li&gt;
&lt;li&gt;Une fois fait, les 2 applications vont pouvoir s'échanger des jetons temporaires qui permettront de demander l'autorisation d'accès.&lt;/li&gt;
&lt;li&gt;Depuis l'application l'utilisateur peut maintenant être redirigé vers le serveur pour accepter ou non l'accès à ses ressources.&lt;/li&gt;
&lt;li&gt;Si le client valide le jeton temporaire, celui ci pourra être converti en jeton d'accès.&lt;/li&gt;
&lt;li&gt;Une fois le jeton enregistré, le client peut interagir avec l'API du serveur au nom de l'utilisateur jusqu'à ce que le
jeton d'accès expire.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il y a donc 3 types de jetons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jetons clients (consumer): permettent d'identifier le client auprès du serveur.&lt;/li&gt;
&lt;li&gt;Jetons temporaires (token request): permettent d'identifier la demande d'autorisation et ne sont pas liés à un utilisateur
en particulier.&lt;/li&gt;
&lt;li&gt;Jetons d'accès (access token): permettent d'identifier l'utilisateur.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le cheminement pour l'utilisateur est donc relativement simple, le service qui demande à accéder aux ressources (client)
redirige vers le service qui propose la ressource (serveur) en spécifiant un callback.&lt;/p&gt;
&lt;p&gt;Une fois l'accès validé par l'utilisateur, le service de destination (serveur) redirige vers le service demandeur (client)
grâce au callback.&lt;/p&gt;
&lt;p&gt;Une fois ce processus terminé le client peut agir au nom de l'utilisateur.&lt;/p&gt;
&lt;p&gt;Il faut noter que les jetons constituent un accès temporaire et n'ont pas vocation à être utilisables ad vitam æternam.&lt;/p&gt;
&lt;p&gt;La &lt;a href="http://www.rfc-editor.org/rfc/rfc5849.txt"&gt;RFC&lt;/a&gt; propose deux méthodes pour signer les requêtes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Secret partagé (symétrique).&lt;/li&gt;
&lt;li&gt;RSA (asymétrique), dans ce cas la sécurité repose sur le secret de la clé privée.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Les versions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;OAuth 1.0 (octobre 2007) &lt;a href="http://fr.readwriteweb.com/2009/04/30/a-la-une/comment-le-monde-du-libre-gagne-bataille-oauth/"&gt;qui ne doit plus être utilisée en raison d'une faille de sécurité&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;OAuth 1.0a (juin 2009) qui corrige ce problème, version la plus largement répandue, utilisé par twitter, gmail, foursquare…&lt;/li&gt;
&lt;li&gt;OAuth 2 (mai 2010), encore en draft, utilisé par facebook.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Les confusions&lt;/h2&gt;
&lt;p&gt;Il ne faut pas confondre authentification et autorisation.
OAuth est un mécanisme d'autorisation là ou &lt;a href="http://fr.wikipedia.org/wiki/OpenID"&gt;openid&lt;/a&gt; se veut un mécanisme d'authentification.
Le premier vise à autoriser là ou le second vise à identifier une personne.&lt;/p&gt;
&lt;h2&gt;Les limitations&lt;/h2&gt;
&lt;p&gt;Il est important de noter que OAuth ne propose pas de mécanisme granulaire d'autorisation à proprement parler, il offre
un mécanisme d'accès à un service mais ce n'est pas lui qui détermine à quoi a accès X. En fait, libre à Y
d'implémenter ce mécanisme et de définir quelles seront les données accessibles.&lt;/p&gt;
&lt;h2&gt;Les risques&lt;/h2&gt;
&lt;p&gt;En terme de sécurité, les différents mécanismes mis en place souffrent de quelques faiblesses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les redirections sont vulnérables au &lt;a href="http://fr.wikipedia.org/wiki/Hameçonnage"&gt;phishing&lt;/a&gt;, notamment au moment de rediriger vers le service où l'utilisateur doit entrer ses
identifiants pour autoriser l'accès.&lt;/li&gt;
&lt;li&gt;La nécessité d'utiliser des secrets partagés expose les clés à une récupération, que ce soit par le biais
d'une écoute du trafic http ou simplement dans le cas de code libre où les jetons clients seront visibles dans le code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Les outils pour ruby&lt;/h2&gt;
&lt;p&gt;S'il y a une gem à avoir dans sa trousse à outil du bon développeur ruby, c'est &lt;a href="https://github.com/intridea/omniauth"&gt;omniauth&lt;/a&gt;
qui s'intègre avec un nombre incroyable de services.&lt;/p&gt;
&lt;p&gt;Cette gem est très bien maintenue et testée.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;OAuth est maintenant un protocole largement répandu et s'est imposé comme la référence. A ce jour il parait impensable
de ne pas l'utiliser si vous souhaitez construire une API avec un accès protégé.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Introduction à coffeescript</title>
    <id>https://martin.catty.fr/blog/introduction-a-coffeescript/</id>
    <link href="https://martin.catty.fr/blog/introduction-a-coffeescript/" rel="alternate"/>
    <updated>2011-08-02T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;C'est quoi coffeescript ?&lt;/h3&gt;
&lt;p&gt;Coffeescript est un langage compilé en JavaScript. Il n'est pas interprété nativement, il doit d'abord être converti en JavaScript afin d'être utilisable.&lt;/p&gt;
&lt;p&gt;Il est à noter que coffeescript sera inclus directement dans &lt;a href="http://www.synbioz.com/blog/2011/07/27/les_principales_nouveautes_de_rails_3_1"&gt;rails 3.1&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Pourquoi l'utiliser ?&lt;/h3&gt;
&lt;p&gt;Quelques arguments avant de rentrer dans le vif du sujet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript généré lisible.&lt;/li&gt;
&lt;li&gt;Syntaxe proche de Ruby, très compréhensible.&lt;/li&gt;
&lt;li&gt;Mécanisme de classe et d'héritage simplifié.&lt;/li&gt;
&lt;li&gt;Inclus dans rails 3.1.&lt;/li&gt;
&lt;li&gt;Volume de code à maintenir moindre.&lt;/li&gt;
&lt;li&gt;Utilisation de concepts avancés simplifiés (classe, binding…).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Syntaxe&lt;/h3&gt;
&lt;p&gt;En coffee, vous n'avez pas besoin d'utiliser explicitement le mot clé var, ni les «;», ceux ci
seront générés automatiquement pour vous.&lt;/p&gt;
&lt;h4&gt;Les idées reprises de Ruby&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Les parenthèses sont facultatives du moment qu'elles ne posent pas de problèmes à l'interprétation.&lt;/li&gt;
&lt;li&gt;Le dernier résultat interprété est retourné, pas besoin de return explicite.&lt;/li&gt;
&lt;li&gt;Il est possible de faire de l'interpolation sur les strings du type: "foo #{bar}" ou bar est une variable.&lt;/li&gt;
&lt;li&gt;Le if peut être placé en fin d'expression.&lt;/li&gt;
&lt;li&gt;Le mot clé unless existe.&lt;/li&gt;
&lt;li&gt;Les commentaires sont définis avec #.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;L'égalité stricte avec le mot clé is&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="c1"&gt;# true&lt;/span&gt;

&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
&lt;span class="c1"&gt;# false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4&gt;Le or= et ?=&lt;/h4&gt;
&lt;p&gt;or= est l'équivalent de ||=&lt;/p&gt;
&lt;p&gt;Attention avec a or= b&lt;/p&gt;
&lt;p&gt;a prendra la valeur de b si a est null, undefined, false, vaut 0 ou "" qui valent tous false
une fois convertis en booléen.&lt;/p&gt;
&lt;p&gt;a ?= b affectera la valeur de b dans a uniquement si a est null ou undefined.&lt;/p&gt;
&lt;p&gt;Il s'agit en fait d'un raccourci syntaxique. Chaque valeur définie peut être testée avec ?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="nf"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="nf"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Equivalences de notation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# équivalences&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unless&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4&gt;Les fonctions&lt;/h4&gt;
&lt;p&gt;A chaque fois que vous voyez &lt;em&gt;-&amp;gt;&lt;/em&gt; il s'agit en fait d'une définition de fonction. C'est un concept
central dans coffeescript.&lt;/p&gt;
&lt;p&gt;Définissions notre première fonction anonyme:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;() -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Hello from coffee"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;donnera en js:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello from coffee"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Si vous avez besoin de pouvoir rappeler votre fonction, ce qui sera souvent le cas, excepté pour les
callbacks, il vous suffit de l'affecter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;cube&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(num) -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;donnera en js:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Je ne vous montre qu'une partie du code généré mais le compilateur généré aussi les déclarations de variable
pour vous et enrobe le tout dans une fonction anonyme (voir portée).&lt;/p&gt;
&lt;p&gt;Nous avons vu comment passer un argument et nommer une fonction.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Important&lt;/em&gt;: la génération des fonctions se fait de la forme suivante:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;et&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;non&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Les deux syntaxes sont complètement valides, mais c'est la première qui a été retenue pour des raisons
de compatibilité avec IE.&lt;/p&gt;
&lt;p&gt;Toutefois celle ci est plus contraignante car vous ne pouvez appeler une
fonction que quand elle a été définie.&lt;/p&gt;
&lt;p&gt;L'ordre de définition est donc très important.&lt;/p&gt;
&lt;h4&gt;Paramètres par défaut&lt;/h4&gt;
&lt;p&gt;Les fonctions offrent la prise en charge des arguments par défaut.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;helloWorld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(street = "Lille") -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Hello world from &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;

&lt;span class="nx"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Hello world from Lille.&lt;/span&gt;
&lt;span class="nx"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Paris"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Hello world from Paris.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4&gt;Splat&lt;/h4&gt;
&lt;p&gt;Les splat permettent de définir un nombre variable d'arguments qui seront récupérés comme un tableau:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(a, b..., c) -&amp;gt;&lt;/span&gt;

&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"baz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# b =&amp;gt; [ 'foo', 'bar', 'baz' ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A noter que les arguments hors splat sont les premiers à être assignés:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# []&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Les splats peuvent être utilisés hors fonction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# [ 'foo', 'bar' ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Portée&lt;/h3&gt;
&lt;p&gt;Le moindre code coffeescript est transformé en un code JavaScript enrobé dans une fonction anonyme.&lt;/p&gt;
&lt;p&gt;Cela présente un avantage et un inconvénient. D'une part vous ne risquez pas de rencontrer des conflits
de nom ou de portée car le code générée ne peut pas écraser un code existant (namespacing).&lt;/p&gt;
&lt;p&gt;D'autre part cela peut poser des problèmes si deux fichiers ont besoin d'interagir.&lt;/p&gt;
&lt;p&gt;Au niveau compilation il est possible d'éviter cela avec l'option &lt;strong&gt;--no-wrap&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Au niveau code il est possible d'utiliser l'objet global window pour affecter les différents modules.&lt;/p&gt;
&lt;p&gt;Par exemple pour déclarer une classe utilisable depuis un autre morceau de code coffee:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;This&lt;/h3&gt;
&lt;p&gt;Il est facile de s'arracher les cheveux si on maitrise mal &lt;a href="http://www.synbioz.com/blog/2011/05/17/le_cote_objet_de_JavaScript"&gt;la portée en JavaScript&lt;/a&gt;.
Posez vous toujours la question: qui est this dans ce contexte ?&lt;/p&gt;
&lt;p&gt;Coffeescript vous offre un raccourci syntaxe pour accèder directement à la propriété
d'un objet:&lt;/p&gt;
&lt;p&gt;@attribute équivaut à this.attribute&lt;/p&gt;
&lt;p&gt;C'est pratique, notamment pour les classes (voir partie Les classes), mais aussi
pour tout objet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;person&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;firstname&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Martin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lastname&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Catty"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;person.introduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Bonjour, je suis &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nx"&gt;@firstname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nx"&gt;@lastname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;
&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;introduce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Bonjour, je suis Martin Catty.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Cette syntaxe vient avec un raccourci syntaxique. Par exemple dans le cas classique
de définition d'un setter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;setAttribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(attribute) -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attribute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;peut être raccourci en:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;setAttribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(@attribute) -&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Coffeescript offre un mécanisme extrèmement simple pour garantir le this actuel.
Plutôt que de définir votre fonction avec -&amp;gt; il suffit d'utiliser =&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Exemple&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;registerEvents&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(node) -&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;(event) =&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Les classes&lt;/h3&gt;
&lt;p&gt;Coffeescript introduit un mécanisme de classe très puissant:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Animal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Animal&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Dog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Dog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Animal&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# convention pour une constante mais utilise var&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;STATES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"sleeping"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"eating"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;constructor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# appelle le constructeur d'Animal&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# méthode de l'objet&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;speak&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"woff."&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# variable de classe&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="vi"&gt;@count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# méthode statique&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="vi"&gt;@incCount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;@count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Utiliser coffeescript avec rails (avant 3.1)&lt;/h3&gt;
&lt;p&gt;La convention est de stocker ses fichiers dans app/coffeescripts/&lt;/p&gt;
&lt;p&gt;Ensuite il s'agit de générer les fichiers correspondant. Pour cela &lt;a href="http://www.synbioz.com/blog/2011/06/15/encore_plus_productif_avec_Ruby_on_rails"&gt;guard&lt;/a&gt; fait très bien l'affaire.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile:&lt;/span&gt;

&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;development&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'guard'&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'guard-coffeescript'&lt;/span&gt;
&lt;span class="nx"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Guardfile&lt;/span&gt;

&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'frontend'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;guard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'coffeescript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'public/JavaScripts/compiled'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;coffeescripts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coffee$&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;
&lt;span class="nx"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bundle&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;guard
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Complément&lt;/h3&gt;
&lt;p&gt;Il est possible d'appeler du js depuis du coffee avec les backticks `.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;L'apprentissage de coffeescript est assez simple, surtout si vous utilisez actuellement Ruby et que vous avez une bonne base en JavaScript.&lt;/p&gt;
&lt;p&gt;Le temps d'apprentissage me semble largement valoir le gain en temps de développement et maintenance.&lt;/p&gt;
&lt;p&gt;Bref, l'essayer c'est l'adopter.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Le devenir de Rails</title>
    <id>https://martin.catty.fr/blog/le-devenir-de-rails/</id>
    <link href="https://martin.catty.fr/blog/le-devenir-de-rails/" rel="alternate"/>
    <updated>2011-07-13T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Récemment &lt;a href="https://plus.google.com/109694994098307678378/posts"&gt;Patrick&lt;/a&gt; m'interpellait à propos d'un article sur &lt;a href="http://blog.stevecoast.com/what-the-hell-is-happening-to-Rails"&gt;le devenir de Rails&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Plutôt que de lui répondre en privé, j'en profite pour livrer mon opinion.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: cet avis est purement personnel.&lt;/p&gt;
&lt;p&gt;Si j'ai vraiment le sentiment que l'auteur cherche à faire un peu de buzz en lançant un gros troll,
cela ne signifie pas que je n'ai rien à reprocher à Ruby on Rails.&lt;/p&gt;
&lt;h3&gt;La courbe d'apprentissage de Rails&lt;/h3&gt;
&lt;p&gt;L'auteur nous signale qu'en venant de PHP la courbe d'apprentissage est complexe, et encore
plus dans les dernières versions de Rails (il cite la 3 et la 3.1).&lt;/p&gt;
&lt;p&gt;Je ne suis absolument pas d'accord. D'une part on ne peut pas mettre sur la même ligne un framework
et un langage. Forcément si vous venez de PHP il sera plus simple d'appréhender un framework basé sur PHP,
que sur Ruby.&lt;/p&gt;
&lt;p&gt;D'autre part, je n'ai jamais entendu personne me dire que Ruby était plus complexe que PHP.&lt;/p&gt;
&lt;p&gt;D'un côté on a un langage lisible, à l'API claire et réellement objet, de l'autre rien de tout ça.
Si vous pensez que j'exagère, je vous invite à lire &lt;a href="http://en.wikiquote.org/wiki/Rasmus_Lerdorf"&gt;quelques citations de l'auteur de PHP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Je ne crois pas que l'apprentissage de Rails 3 soit plus compliqué que celui de Rails 2.
Par contre je conçois très clairement que passer de la version 2 à la 3 nécessite un investissement important.&lt;/p&gt;
&lt;p&gt;C'est pour cela que &lt;a href="http://www.synbioz.com/blog/2011/05/19/retour_sur_nos_presentations_de_Rails_a_supinfo"&gt;lors de mes présentations&lt;/a&gt;
j'insiste sur Ruby on Rails 2.x car la majorité des développeurs Rails à venir aura certainement à travailler sur des applications Rails 2.3 existantes.&lt;/p&gt;
&lt;p&gt;S'ils n'en ont jamais vu, car uniquement formés sur Rails 3, cela risque d'être la douche froide.&lt;/p&gt;
&lt;p&gt;Dans tous les cas une expérience sur Ruby on Rails ne s'improvise pas.&lt;/p&gt;
&lt;p&gt;Commencer avec Ruby on Rails c'est l'utiliser, maitriser Ruby on Rails c'est le comprendre.&lt;/p&gt;
&lt;h3&gt;L'absence de route par défaut&lt;/h3&gt;
&lt;p&gt;Je crois que c'est le plus mauvais argument que j'ai pu lire. Rails est bâti sur un certain nombre
de conventions (conventions over configuration), n'importe quel bon développeur sait qu'il est préférable
de designer une application restful, c'est à dire basée sur une approche ressources.&lt;/p&gt;
&lt;p&gt;L'idée est d'exploiter pleinement le potentiel du protocole HTTP en mappant (ou en émulant) les verbes
HTTP sur les actions de CRUD (post pour le create, get pour le read, put pour l'update et delete pour le destroy).&lt;/p&gt;
&lt;h3&gt;Les helpers javascript&lt;/h3&gt;
&lt;p&gt;Leur disparition ne peut qu'améliorer la base de code de Rails. C'était très clairement une très mauvaise idée,
qui avait privilégié la simplicité au dépend de la qualité.&lt;/p&gt;
&lt;p&gt;La couche javascript &lt;strong&gt;ne doit pas&lt;/strong&gt; et n'aurait jamais dû être présente dans les vues.
La gestion événementielle doit être déportée dans des fichiers séparés, qui pourront être minifiés et cachés.&lt;/p&gt;
&lt;h3&gt;Les choix par défaut de Rails&lt;/h3&gt;
&lt;p&gt;Rails, à l'instar de Freebsd ou Linux sont gérés, désolé pour l'oxymore, par des "dictateurs bienveillants".
Cela confère des avantages et des inconvénients. D'une part le projet évolue rapidement avec une certaine ligne
directrice, d'autre part on peut se retrouver en opposition avec un choix.&lt;/p&gt;
&lt;p&gt;Dans le cas de coffeescript je rejoins l'auteur. Je ne suis pas gêné par ce choix, car nous utilisons de toute façon
coffeescript, mais je ne vois pas l'utilité d'imposer une surcouche dans la stack par défaut de Rails.&lt;/p&gt;
&lt;p&gt;Il en va de même pour haml / sass. Ce sont des choix très personnels, qui devraient le rester.&lt;/p&gt;
&lt;p&gt;Nombreux sont ceux qui reprochent à Rails de vouloir être trop tendance en incluant les libs ou surcouche à la mode.
Je ne crois pas que ce soit l'idée. La core team Rails fait le choix d'introduire certains éléments parce qu'elle les considèrent
comme facilitant le développement, productifs, plus adaptés ou répandus (par exemple pour le remplacement de prototype par jquery par défaut).&lt;/p&gt;
&lt;p&gt;Chaque choix est discutable mais l'essentiel à mon sens reste d'avoir le choix.&lt;/p&gt;
&lt;p&gt;C'est l'un des principaux atouts de Rails 3, être indépendant à un grand nombre de niveaux, que ce soit dans le choix
du mapping objet relationnel, de la bibliothèque js, du système de tests…&lt;/p&gt;
&lt;p&gt;De même on peut maintenant configurer son framework aux petits oignons et charger uniquement les composants requis ;
permettant à Rails de développer aussi bien des petites que des grosses applications.&lt;/p&gt;
&lt;h3&gt;La documentation&lt;/h3&gt;
&lt;p&gt;Forcément si on utilise la version edge de Rails pour essayer d'écrire un livre cela risque d'être compliqué.&lt;/p&gt;
&lt;p&gt;Je pense qu'il s'agit vraiment d'un faux procès car on a quand même beaucoup de documentation pour Rails. Si
on regroupe &lt;a href="http://guides.RubyonRails.org/"&gt;les guides&lt;/a&gt;, &lt;a href="http://apidock.com/Rails"&gt;l'api&lt;/a&gt;, &lt;a href="http://Railscasts.com/"&gt;les screencasts&lt;/a&gt;,
&lt;a href="http://Railstutorial.org/book"&gt;les tutoriaux&lt;/a&gt;, et tous les éléments d'informations qu'on peut trouver via &lt;a href="http://www.Rubyflow.com/"&gt;Rubyflow&lt;/a&gt; cela donne une source
d'information considérable.&lt;/p&gt;
&lt;h3&gt;Reproche: l'aspect performance&lt;/h3&gt;
&lt;p&gt;Loin de moi l'idée de vouer un culte à Rails. D'ailleurs il y a vraiment une chose qui m'irrite: les régressions de performance.&lt;/p&gt;
&lt;p&gt;Quand je vois ce &lt;a href="https://twitter.com/thomasfuchs/status/89447019906732036"&gt;tweet de Thomas Fuchs&lt;/a&gt;, cela me fait bondir.
Clairement, un framework qui se veut professionnel devrait intégrer un certain nombre de tests de performances et une release ne devrait pas pouvoir sortir s'il elle ne remplit
pas ces critères.&lt;/p&gt;
&lt;p&gt;On ne peut pas transiger là dessus.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;La volonté de Rails est, il me semble, de simplifier la vie du développeur. Parfois il s'agit aussi de
pousser les bonnes pratiques (par exemple pour la minification en 3.1).&lt;/p&gt;
&lt;p&gt;Sa force a toujours été d'avoir une approche pragmatique, donc basée sur des usages réels, et je continuerai
à l'utiliser tant que son développement ira dans ce sens.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Encore plus productif avec ruby on rails</title>
    <id>https://martin.catty.fr/blog/encore-plus-productif-avec-ruby-on-rails/</id>
    <link href="https://martin.catty.fr/blog/encore-plus-productif-avec-ruby-on-rails/" rel="alternate"/>
    <updated>2011-06-15T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Améliorer son environnement de développement Ruby on Rails&lt;/h2&gt;
&lt;p&gt;Si vous utilisez déjà Ruby on Rails (si vous hésitez encore, jetez un œil
à &lt;a href="http://www.synbioz.com/blog/2011/01/11/pourquoi_choisir_ruby_on_rails_pour_le_web"&gt;pourquoi choisir ruby on rails&lt;/a&gt;)
vous vous dites sûrement que rails est déjà très productif out of the box, et vous avez raison.&lt;/p&gt;
&lt;p&gt;Base de donnée, serveur embarqué, nul besoin d'un IDE ; tout est là pour commencer à prototyper rapidement.
Mais les outils évoluent et il est possible de faire encore mieux.&lt;/p&gt;
&lt;h3&gt;Pow&lt;/h3&gt;
&lt;p&gt;Pow est un outil qui embarque à la fois un mini serveur DNS et HTTP.
L'installation se borne à l'exécution d'un script sh.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;get.pow.cx&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Le script va récupérer la dernière version et s'installer au démarrage.
Attention le script nécessite mac os x 10.6 pour fonctionner, même si
&lt;a href="https://github.com/ysbaddaden/pow"&gt;une branche linux existe sur ce fork&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;L'objectif de pow est de charger vos applications à la demande, il suffit pour cela de faire
un lien symbolique dans le dossier ~/.pow&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/mon/app&lt;span class="w"&gt; &lt;/span&gt;~/.pow/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Une fois réalisé, faites pointer votre navigateur sur &lt;a href="http://app.dev"&gt;http://app.dev&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Le serveur web embarqué est nack. C'est un serveur rack basé sur node.js&lt;/p&gt;
&lt;p&gt;Pour les applications rails 3 il n'y a rien à faire, pow s'accommode parfaitement de
bundler et rvm.&lt;/p&gt;
&lt;p&gt;Pour les applications rails 2 il faut fournir un fichier rackup, qui explique à
n'importe quel serveur compatible rack comment booter l'application.&lt;/p&gt;
&lt;p&gt;Ce fichier se présente sous cette forme:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/config/environment'&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Dispatcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Au delà d'un certain temps (5 min par défaut, configurable dans ~/.powconfig)
d'utilisation, l'app sera arrêtée et déchargée.&lt;/p&gt;
&lt;p&gt;Si vous avez besoin de forcer le re-chargement de l'application il suffit de faire un:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;touch&lt;span class="w"&gt; &lt;/span&gt;tmp/restart.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;comme avec passenger. Mais le rechargement forcé devrait être de l'histoire ancienne
grâce à l'outil suivant.&lt;/p&gt;
&lt;h3&gt;Guard&lt;/h3&gt;
&lt;p&gt;Guard est un outil de détection et de gestion des événements relatifs au système de fichier.
Sa force est d'être cross-platform en s'appuyant sur des outils éprouvés selon le type d'OS.&lt;/p&gt;
&lt;p&gt;Cela permet par exemple d'exécuter une action lors du changement d'un fichier. Exemples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Concaténer et minifier le javascript de production lorsque les fichiers sources sont modifiés.&lt;/li&gt;
&lt;li&gt;Relancer la série de tests automatiquement à la sauvegarde des fichiers de test.&lt;/li&gt;
&lt;li&gt;Relancer le serveur web lorsqu'un fichier de conf est modifié
…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Guard s'installe comme n'importe quelle gem, il suffit de l'ajouter au Gemfile.&lt;/p&gt;
&lt;p&gt;Ensuite on crée notre Guardfile avec: guard init.&lt;/p&gt;
&lt;p&gt;Guard possède un certain nombre de modules complémentaires. Il y en a par exemple un pour s'interfacer avec pow.
Dans le Gemfile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'guard-pow'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Et enfin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Cela vous crée le fichier Guardfile suivant:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'pow'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.powrc'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.powenv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.rvmrc'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Gemfile'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Gemfile.lock'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'config/application.rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'config/environment.rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;%r{^config/environments/.*\.rb$}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;%r{^config/initializers/.*\.rb$}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;À la modification de l'un de ces fichiers guard va donc relancer pow automatiquement.&lt;/p&gt;
&lt;h3&gt;Livereload&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/mockko/livereload"&gt;Livereload&lt;/a&gt; pousse l'automatisation encore un peu plus loin.&lt;/p&gt;
&lt;p&gt;Une fois la gem et l'extension (disponible pour safari, chrome, firefox 4) installée, dans le
répertoire de l'application il suffit de lancer livereload en console puis de connecter le navigateur.&lt;/p&gt;
&lt;p&gt;Une fois fait, chaque changement dans un fichier observé (vue, css, js…) entrainera un rechargement automatique du
navigateur.&lt;/p&gt;
&lt;p&gt;Il est bien sûr possible d'observer différents types de fichiers en ajoutant leur extension dans le
fichier .livereload.&lt;/p&gt;
&lt;p&gt;Livereload utilise les websocket pour communiquer avec le navigateur.&lt;/p&gt;
&lt;p&gt;Enfin, il existe guard-livereload, qui s'installe comme les autres gem, pour ne plus lancer livereload manuellement.&lt;/p&gt;
&lt;p&gt;Au final au lieu de lancer un rails server votre commande par défaut deviendra:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bundle&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;guard
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>Visibilité, introspection, méta-programmation et réflexion avec ruby</title>
    <id>https://martin.catty.fr/blog/visibilite-introspection-meta-programmation-et-reflexion-avec-ruby/</id>
    <link href="https://martin.catty.fr/blog/visibilite-introspection-meta-programmation-et-reflexion-avec-ruby/" rel="alternate"/>
    <updated>2011-05-31T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Une visibilité différente&lt;/h2&gt;
&lt;h3&gt;Les méthodes publiques&lt;/h3&gt;
&lt;p&gt;C'est la visibilité par défaut d'une méthode, elle n'a pas de particularité.&lt;/p&gt;
&lt;h3&gt;Les méthodes privées&lt;/h3&gt;
&lt;p&gt;Contrairement à d'autres langages, les méthodes privées sont aussi héritées dans les classes descendantes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Animal&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;attr_accessor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:weight&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@weight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@age&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"I'm &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, I have &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; years and I weigh &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; KG."&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;older?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;bigger?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:age&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:weight&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Dog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"I'm &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, I have &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; years and I weigh &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (but I have lost some kg)."&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Animal#weight est privée, pourtant si j'appelle le Cat#to_s, j'aurai bien
"I'm garfield, I have 3 years and I weigh 10 (but I have lost some kg)." ce qui signifie que l'on a bien accès
à la méthode.&lt;/p&gt;
&lt;p&gt;Les autres méthodes de la classe peuvent accéder à la méthode privée, mais uniquement pour l'objet courant.
Dans l'exemple garfield.bigger?(snoopy) renverra une erreur car je ne peux pas invoquer weight sur snoopy depuis garfield.&lt;/p&gt;
&lt;p&gt;Cette notion de contexte est très importante car même l'invocation sur self ne fonctionnera pas. Si
dans mon to_s j'utilisais self.weight j'aurai droit à une erreur d'invocation de méthode privée.&lt;/p&gt;
&lt;h3&gt;Les méthodes protected&lt;/h3&gt;
&lt;p&gt;Contrairement à d'autres langage comme java, le fait de définir une méthode protected,
ne signifie pas qu'elle pourra uniquement être appelée dans une classe descendante. On a vu que même
les méthodes privées étaient héritées.&lt;/p&gt;
&lt;p&gt;En effet en ruby une méthode protected est très proche d'une méthode privée. La nuance se trouve au niveau
du contexte. Si on a vu que l'invocation de bigger? renvoyait une erreur, dans le cadre d'un attribut protected
comme age cela fonctionne, je peux donc invoquer Animal#older?&lt;/p&gt;
&lt;p&gt;Les attributs protégés sont donc accessibles aux autres objets de la classe mais aussi aux objets
de classe héritées. Par exemple ici je peux accéder à Dog#weight depuis Cat#weight car ils descendent
tout deux de Animal.&lt;/p&gt;
&lt;h2&gt;Introspection&lt;/h2&gt;
&lt;p&gt;L'introspection est la capacité pour un objet de s'auto-examiner:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
&lt;span class="c1"&gt;# Cat&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ancestors&lt;/span&gt;
&lt;span class="c1"&gt;# [Cat, Animal, Object, Kernel]&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;
&lt;span class="c1"&gt;# ["==", "===", "=~", "__id__", "__send__", "age", "age=", "bigger?", "class", "clone", "display", "dup", "enum_for", "eql?", "equal?", "extend", "freeze", "frozen?", "hash", "id", "inspect", "instance_eval", "instance_exec", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "instance_variables", "is_a?", "kind_of?", "method", "methods", "name", "name=", "nil?", "object_id", "older?", "private_methods", "protected_methods", "public_methods", "respond_to?", "send", "singleton_methods", "taint", "tainted?", "tap", "to_a", "to_enum", "to_s", "type", "untaint", "weight="]&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# []&lt;/span&gt;

&lt;span class="c1"&gt;# Object.const_get("RUBY_VERSION")&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ancestors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"RUBY_VERSION"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1.8.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;On peut par exemple lister toute la hiérarchie de classes parentes, tout comme les constantes et les méthodes
définies. Le paramètre false sur methods permet de ne lister que nos propres méthodes et pas celles héritées.&lt;/p&gt;
&lt;p&gt;Ici Cat ne définit aucune méthode qui lui est propre, d'où le tableau vide.&lt;/p&gt;
&lt;h2&gt;Méta-programmation&lt;/h2&gt;
&lt;p&gt;Il faut visualiser la méta-programmation comme du code qui crée du code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Cat&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;STATES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hungry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sated"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;freeze&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;attr_accessor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:state&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;STATES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hungry"&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hungry?&lt;/span&gt;
&lt;span class="c1"&gt;# true&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sated?&lt;/span&gt;
&lt;span class="c1"&gt;# false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ici je peux ré-ouvrir ma class Cat à la volée et y définir des méthodes en fonction d'attributs existants.
La clé se trouve dans le define_method qui permet de créer des méthodes dynamiquement.&lt;/p&gt;
&lt;h2&gt;Réflexion&lt;/h2&gt;
&lt;p&gt;On a vu dans la première partie des méthodes à la visibilité privée. Toutefois en ruby il n'est pas possible
de complètement cacher une méthode.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:weight&lt;/span&gt;
&lt;span class="c1"&gt;# 10&lt;/span&gt;
&lt;span class="no"&gt;Cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:public&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:weight&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;garfield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;
&lt;span class="c1"&gt;# 10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Celle ci peut toujours être invoquée avec send. De même il est possible de changer la visibilité d'une méthode
au runtime ! Vous l'aurez compris, ces concepts très puissants sont à utiliser avec parcimonie.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Retour sur nos présentations de rails à Supinfo</title>
    <id>https://martin.catty.fr/blog/retour-sur-nos-presentations-de-rails-a-supinfo/</id>
    <link href="https://martin.catty.fr/blog/retour-sur-nos-presentations-de-rails-a-supinfo/" rel="alternate"/>
    <updated>2011-05-19T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h3&gt;La situation de ruby en france&lt;/h3&gt;
&lt;p&gt;Chez Synbioz, nous avons le sentiment que la demande autour de ruby et ruby on rails s'accroît et que l'offre
a de plus en plus de mal à suivre.&lt;/p&gt;
&lt;p&gt;Là où 3-4 ans en arrière il fallait convaincre de l'intérêt et de la puissance de la technologie, aujourd'hui
certains clients nous la réclame spontanément.&lt;/p&gt;
&lt;p&gt;Nombreuses sont les sociétés à vouloir internaliser des profils rubyistes, et vous diront ô combien c'est
difficile. Le plus étonnant vient du fait que généralement ceux qui se lancent sur cette technologie sont séduits
et n'envisagent pas de faire machine arrière.&lt;/p&gt;
&lt;p&gt;Dans l'équation nous avons donc des porteurs de projets friands de la technologie, des développeurs heureux de
l'utiliser mais une chose ne suit pas: la formation.&lt;/p&gt;
&lt;p&gt;Les rubyistes seniors actuels sont ceux qui ont pris sur leur temps personnel pour se former et découvrir cette technologie.
Parfois le déclic (ce fût mon cas) aura été une présentation. Une simple introduction permet d'éveiller la curiosité.&lt;/p&gt;
&lt;p&gt;J'avais donc envie de rendre un peu de ce qui m'avait été donné.&lt;/p&gt;
&lt;p&gt;Le domaine de l'éducation est assez complexe et immobiliste en général, c'est pourquoi j'ai
proposé de sensibiliser les futurs ingénieurs à cette technologie par le biais de conférences.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.supinfo.com"&gt;Supinfo&lt;/a&gt; a accepté ma proposition et je les en remercie.&lt;/p&gt;
&lt;h3&gt;La présentation&lt;/h3&gt;
&lt;p&gt;Je suis donc intervenu au Supinfo de Valenciennes puis Lille. Ma présentation se divise en 2 parties: une première
approche de ruby puis une présentation de rails.&lt;/p&gt;
&lt;p&gt;J'ai pris le parti de présenter rails 2.3 qui est, d'après mon expérience, la version la plus répandue à l'heure
actuelle. Bien sûr il était tentant de présenter rails 3, mais j'ai privilégié l'approche pragmatique.&lt;/p&gt;
&lt;p&gt;D'autant plus que j'ai le sentiment qu'une fois les fondements de rails 2.x intégrés, il est facile de passer
à rails 3, là où un retour en arrière semble plus complexe.&lt;/p&gt;
&lt;p&gt;La présentation est disponible sous plusieurs formats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://bit.ly/keynote-rails"&gt;Keynote de présentation de Ruby on Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://bit.ly/ppt-rails"&gt;Powerpoint de présentation de Ruby on Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://bit.ly/rails-pdf"&gt;PDF de présentation de Ruby on Rails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ou directement visible:&lt;/p&gt;
&lt;div style="width:425px" id="__ss_8051122"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/synbioz/ruby-on-rails-presentation" title="Ruby on rails presentation"&gt;Ruby on rails presentation&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse8051122" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=support-110521070712-phpapp01&amp;stripped_title=ruby-on-rails-presentation&amp;userName=synbioz" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse8051122" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=support-110521070712-phpapp01&amp;stripped_title=ruby-on-rails-presentation&amp;userName=synbioz" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/synbioz"&gt;synbioz&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;La présentation a été très bien accueillie par les étudiants et j'ai eu droit à des questions très pertinentes.
Il n'est pas évident de proposer une présentation qui s'adapte à la plupart des publics: de celui qui n'a jamais
entendu parler de ruby à celui qui a déjà fait une première application en rails.&lt;/p&gt;
&lt;p&gt;J'ai ajouté à cette présentation &lt;a href="http://bit.ly/rails-examples"&gt;10 exemples&lt;/a&gt;
qui permettent de visualiser les concepts évoqués et d'aller un peu plus loin.&lt;/p&gt;
&lt;p&gt;Pour tout retour, question ou suggestion d'amélioration vous pouvez m'adresser vos remarques à l'email
noté dans la seconde slide du support. N'hésitez pas, c'est toujours agréable d'avoir du feedback.&lt;/p&gt;
&lt;p&gt;Cette présentation est sous licence &lt;a href="http://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;CC BY-NC-SA 2.0&lt;/a&gt;.
Vous pouvez donc modifier et distribuer cette présentation, sous réserve de citer l'auteur, ne pas en faire
un usage commercial, et conserver cette licence si vous partager votre version modifiée.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Le côté objet de javascript</title>
    <id>https://martin.catty.fr/blog/le-cote-objet-de-javascript/</id>
    <link href="https://martin.catty.fr/blog/le-cote-objet-de-javascript/" rel="alternate"/>
    <updated>2011-05-17T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;p&gt;Javascript est un langage objet, même s'il ne possède pas un modèle de programmation par classes mais une modèle orienté prototype.&lt;/p&gt;
&lt;p&gt;Un objet n'est donc pas une instance de classe mais un clone de prototype, et peut être modifié «à chaud».&lt;/p&gt;
&lt;p&gt;Ce concept est extrêmement puissant &lt;sup&gt;&lt;a href="#great_power_great_responsibility"&gt;[1]&lt;/a&gt;&lt;/sup&gt;, puisque l'héritage est dynamique, c'est à dire qu'un objet peut changer de parent en cours de route (cf. section Héritage).&lt;/p&gt;
&lt;p&gt;Le fait d'ajouter une fonction dans le prototype, rend cette fonction disponible immédiatement dans l'ensemble des clones.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"z"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nx"&gt;a1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;
&lt;span class="nx"&gt;a2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Javascript, un langage tout objet&lt;/h3&gt;
&lt;p&gt;Javascript est un langage interprété et typé dynamiquement.
Il existe deux moyens de connaitre le type d'un objet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="ow"&gt;typeof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Boolean"&lt;/span&gt;

&lt;span class="ow"&gt;typeof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;
&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Function"&lt;/span&gt;

&lt;span class="ow"&gt;typeof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Function&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Function représente donc la base de l'héritage.&lt;/p&gt;
&lt;h3&gt;Visibilité et portée&lt;/h3&gt;
&lt;p&gt;Il est possible au sein d'une fonction de définir des variables ou méthodes
qui ne seront pas accessibles de l'extérieur.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// private&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_foo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// public&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// public method returning private attribute&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_foo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_foo&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"undefined"&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bar&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;

&lt;span class="c1"&gt;// static method&lt;/span&gt;
&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// TypeError: a.parse is not a function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Simuler la notion de packages&lt;/h3&gt;
&lt;p&gt;La notion de namespace n'existe pas à proprement parler en Javascript mais elle est très simple
à simuler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;synbioz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;synbioz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;synbioz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Héritage&lt;/h3&gt;
&lt;p&gt;Il faut imaginer la méthode prototype comme un pointeur vers le constructeur
dans un héritage par classe.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Human&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bonjour"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Monster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grrrr"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hulk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;// hulk inherits from Human&lt;/span&gt;
&lt;span class="nx"&gt;Hulk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Human&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;hulk1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hulk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;hulk1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bonjour"&lt;/span&gt;

&lt;span class="c1"&gt;// hulk inherits from Monster&lt;/span&gt;
&lt;span class="nx"&gt;Hulk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Monster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;hulk2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hulk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;hulk2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grrrr"&lt;/span&gt;

&lt;span class="c1"&gt;// warning:&lt;/span&gt;
&lt;span class="nx"&gt;hulk1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bonjour"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Le changement de constructeur n'impacte pas les objets déjà créés car la
propriété prototype de hulk1 n'a pas été modifiée, c'est le prototype du
parent (Hulk) qui a été modifié.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// still does'nt work&lt;/span&gt;
&lt;span class="nx"&gt;hulk1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Monster&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;hulk1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bonjour"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ce code n'a logiquement aucun effet, on ne peut pas modifier directement
le prototype de l'objet, cela reviendrait à vouloir modifier le constructeur
d'une instance dans un héritage par classe.&lt;/p&gt;
&lt;p&gt;Toutefois, dans certaines implémentations (ex: mozilla), l'objet garde une référence vers le prototype parent.&lt;/p&gt;
&lt;p&gt;Attention, la propriété n'est pas standard et ne fonctionne pas sur tous les navigateurs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;hulk1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__proto__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Monster&lt;/span&gt;
&lt;span class="nx"&gt;hulk1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grrrr"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p id="great_power_great_responsibility" name="great_power_great_responsibility"&gt;[1]: Attention, Un grand pouvoir implique de grandes responsabilités.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Algorithme déterministe de génération de couleurs</title>
    <id>https://martin.catty.fr/blog/algorithme-deterministe-de-generation-de-couleurs/</id>
    <link href="https://martin.catty.fr/blog/algorithme-deterministe-de-generation-de-couleurs/" rel="alternate"/>
    <updated>2011-03-08T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Le problème: générer une suite déterministe de nombres&lt;/h2&gt;
&lt;p&gt;Dans un programme il arrive parfois que vous ayez à générer une suite de nombres
aléatoire. Pour cela rand fait très bien l'affaire.&lt;/p&gt;
&lt;p&gt;Par contre vous pouvez avoir besoin de générer une suite de nombres déterministes,
c'est à dire que la suite générée sera toujours la même.&lt;/p&gt;
&lt;p&gt;Dans mon cas il s'agit de générer un tableau de couleurs pour un chart. Le nombre
de couleurs n'est pas défini à l'avance mais je souhaite que les couleurs générées
soit toujours les même pour éviter que le graphique change complètement à chaque
rafraichissement.&lt;/p&gt;
&lt;h2&gt;Le fonctionnement de rand&lt;/h2&gt;
&lt;p&gt;Lorsqu'on utilise un générateur comme rand il commence par initialiser son seed,
un nombre sur lequel sera basé la génération des nombres.&lt;/p&gt;
&lt;p&gt;Ce nombre est normalement initialisé de façon complètement aléatoire, en se basant
sur /dev/random ou /dev/urandom (sur les systèmes UNIX) pour maximiser l'entropie des nombres générés.&lt;/p&gt;
&lt;p&gt;Le but étant que les nombres générés soient le moins «devinable» possible.&lt;/p&gt;
&lt;p&gt;Dans notre cas nous voulons une suite déterministe, nous allons donc initialiser le
seed nous même.&lt;/p&gt;
&lt;p&gt;Pour cela en ruby 1.8 on peut utiliser srand:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;srand 42
a = [rand, rand, rand]
srand 42
[rand, rand, rand] == a
=&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En ruby 1.9 on a la classe Random directement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;generator = Random.new(42)
generator.rand
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;La génération de couleurs&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#%02x%02x%02x"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;hsv_to_rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;h_i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h_i&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h_i&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;srand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hsv_to_rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Plutôt que d'utiliser une génération pure de 3 couleurs avec rand(256) on utilise la
représentation hsv (tsv en français) des couleurs: teinte, saturation, valeur.&lt;/p&gt;
&lt;p&gt;Le premier paramètre code la couleur tandis que les suivant codent l'intensité et la
brillance. L'intérêt est de générer des couleurs dans les même tons, pour éviter
d'avoir des couleurs pastel et d'autres très vives dans le chart.&lt;/p&gt;
&lt;p&gt;Vous pouvez donc ajuster manuellement votre teinte et votre saturation. Dans l'exemple on a 0.5 et 0.3.&lt;/p&gt;
&lt;p&gt;Je ne vais pas vous expliquer l'algorithme de conversion &lt;code&gt;hsv_to_rgb&lt;/code&gt;, je me suis servi de
l'article de wikipedia:
&lt;a href="http://fr.wikipedia.org/wiki/Teinte_Saturation_Valeur"&gt;Teinte Saturation Valeur - Wikipédia&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pour le reste j'appelle colors en lui passant &lt;code&gt;hsv_to_rgb&lt;/code&gt; en block.
Une fois mes 3 couleurs obtenues, je transforme rgb en notation hexadécimale.
Si la notation vous surprend jetez un œil du côté de sprintf.&lt;/p&gt;
&lt;p&gt;Précision importante si vous ne lisez pas l'article de wikipedia, l'algorithme de conversion
RGB vers TSV n'est pas réversible, vous pouvez donc représenter toutes les couleurs RGB en notation
TSV mais pas l'inverse (car l'espace TSV contient plus de couleurs que celui RGB).&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Place aux middlewares avec rack</title>
    <id>https://martin.catty.fr/blog/place-aux-middlewares-avec-rack/</id>
    <link href="https://martin.catty.fr/blog/place-aux-middlewares-avec-rack/" rel="alternate"/>
    <updated>2011-01-04T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Créer son middleware&lt;/h2&gt;
&lt;p&gt;Dans mon &lt;a href="http://www.synbioz.com/blog/2010/12/28/introduction_a_rack"&gt;introduction à rack&lt;/a&gt; je vous ai laissé sur votre faim concernant la création de middleware.&lt;/p&gt;
&lt;p&gt;Nous allons donc voir comment faire un mini-logger.&lt;/p&gt;
&lt;p&gt;Tout d'abord ne pas oublier de rajouter le dossier lib dans l'autoload:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;App&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoload_paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;%W(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;/lib)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ensuite on crée une class Tracking dans notre dossier lib:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Tracking&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"custom.log"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IP: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, PATH: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, PARAMS: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Et voilà notre middleware d'une 10aine de ligne.&lt;/p&gt;
&lt;p&gt;Quelques explications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le constructeur se contente de placer l'objet app dans une variable d'instance pour pouvoir y accéder
depuis call.&lt;/li&gt;
&lt;li&gt;call prend l'environnement en entrée. Les middlewares sont chaînés, ce qui permet de modifier l'environnement
d'un middleware à l'autre.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Par exemple je pourrai définir env['foo'] = 'bar' et y avoir accès dans le middleware suivant.&lt;/p&gt;
&lt;p&gt;L'invocation  de app.call(env) va renvoyer le status de la requête (code http, ex: 200), les entêtes et la réponse.
Le middleware peut donc modifier ces 3 éléments.&lt;/p&gt;
&lt;p&gt;Ici nous nous contentons de parser la requête à partir de l'environnement entrant à l'aide de rack. Ensuite nous
avons accès à toutes les méthodes classiques de request.&lt;/p&gt;
&lt;h2&gt;Inclure son middleware&lt;/h2&gt;
&lt;p&gt;Dans notre fichier d'environnement, par exemple developement.rb il suffit de définir les middlewares de la sorte:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Rails::Rack::Logger"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ActionDispatch::ParamsParser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tracking"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# config.middleware.use("Tracking")&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Il est important de bien utiliser les versions quotées des classes. L'utilisation de la constante entrainerait une exception,
car la classe ne serait pas encore chargée.&lt;/p&gt;
&lt;p&gt;On a ici commencé par retirer le logger de rack pour ajouter notre middleware après le parser.
La dernière ligne montre l'utilisation de use, qui permet d'ajouter le middleware en fin de liste.&lt;/p&gt;
&lt;p&gt;On remarque que Rails 3 est vraiment modulable, l'ajout ou la suppression d'un middleware est très simple, rendant le framework
très souple.&lt;/p&gt;
&lt;h2&gt;Analyse d'un middleware existant: Etag&lt;/h2&gt;
&lt;p&gt;Bon, notre middleware est vraiment basique, mais vous allez voir qu'un middleware plus pratique tel qu'Etag ne fait pas grand chose
de plus compliqué.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'digest/md5'&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Rack&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Automatically sets the ETag header on all String bodies&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ETag&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has_key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ETag'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;digest_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ETag'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;%("&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;")&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kp"&gt;private&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;digest_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MD5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;L'initialisation est identique. Le call va récupérer le status, les entêtes et la requête.&lt;/p&gt;
&lt;p&gt;Ensuite le contenu de la requête est hashé et un entête Etag avec le checksum MD5 ajouté, ce qui permettra au serveur de servir directement le contenu s'il
a déjà été calculé.&lt;/p&gt;
&lt;p&gt;Et c'est tout !&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Introduction à rack</title>
    <id>https://martin.catty.fr/blog/introduction-a-rack/</id>
    <link href="https://martin.catty.fr/blog/introduction-a-rack/" rel="alternate"/>
    <updated>2010-12-28T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Qu'est ce que rack ?&lt;/h2&gt;
&lt;p&gt;Rack est une interface entre votre application et votre serveur web. Son but est d'unifier les API pour simplifier la communication entre l'application et le serveur web.
Grâce à rack vous pouvez:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;interchanger votre serveur, pourvu qu'il soit rack compatible (comme thin, unicorn, passenger…)&lt;/li&gt;
&lt;li&gt;extraire un morceau d'application pour la transformer en application autonome&lt;/li&gt;
&lt;li&gt;intégrer une application existante dans une autre application (ex: une application sinatra dans une application rails)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rack porte bien son nom et son logo puisque l'idée sous jacente est de pouvoir empiler des applications qui vont prendre en entrée la requête pour préparer la réponse.&lt;/p&gt;
&lt;p&gt;Dans rails 3, chaque contrôleur est une application rack.&lt;/p&gt;
&lt;p&gt;Exemple d'app rack basique:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ContentLength&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'text/html'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Hello World'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ce petit bout de code est une app rack en soit et peut être lancé via:&lt;/p&gt;
&lt;p&gt;rackup app.ru -s thin -p 4000&lt;/p&gt;
&lt;p&gt;équivalent à:&lt;/p&gt;
&lt;p&gt;thin -R app.ru -p 4000 start&lt;/p&gt;
&lt;h2&gt;Rack monte à bord de rails&lt;/h2&gt;
&lt;p&gt;Rails 3 étant compatible rack, vous pouvez parfaitement mapper une url directement sur votre mini-app rack:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;WelcomeApp&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# lib/welcome_app.rb&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;WelcomeApp&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'text/html'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Hello World'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(N'oubliez pas de mettre le dossier lib dans l'autoload_path)&lt;/p&gt;
&lt;h2&gt;Un empilement de middlewares&lt;/h2&gt;
&lt;p&gt;Imaginez rails 3 comme une chaîne de middlewares, chacun ayant un travail bien précis à réaliser.
Par exemple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parser la requête pour extraire le path, les params etc…&lt;/li&gt;
&lt;li&gt;Gérer les cookies&lt;/li&gt;
&lt;li&gt;Gérer la connexion à la base de données&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour avoir un listing complet, en console:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Pas moins de 18 middlewares sont déjà chargés de base.&lt;/p&gt;
&lt;p&gt;Tous agissent de façon autonome, ce qui garantit une grande modularité et granularité.
Par exemple si vous souhaitez créer une application web qui aura juste quelques contrôleurs et qui ne
sauve pas de données en base, vous pouvez charger uniquement ce dont vous avez besoin dans votre config/application.rb.&lt;/p&gt;
&lt;p&gt;Par défaut à la création d'une application rails 3 on a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'rails/all'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ce qui nous charge les middlewares suivants:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Strategy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LocalCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ShowExceptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RemoteIp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sendfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConnectionAdapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConnectionManagement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueryCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cookies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Session&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CookieStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Flash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ParamsParser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MethodOverride&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BestStandardsSupport&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Si on décide de charger uniquement action_controller:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"action_controller/railtie"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Strategy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LocalCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ShowExceptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RemoteIp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sendfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cookies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Session&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CookieStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Flash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ParamsParser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MethodOverride&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BestStandardsSupport&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Les deux middlewares ActiveRecord::ConnectionAdapters::ConnectionManagement et ActiveRecord::QueryCache ne sont plus chargés.&lt;/p&gt;
&lt;p&gt;Dans notre prochain article sur rack nous verrons comment créer son propre middleware.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Insérer un tableau dans le dom avec Internet Explorer 7</title>
    <id>https://martin.catty.fr/blog/inserer-un-tableau-dans-le-dom-avec-internet-explorer-7/</id>
    <link href="https://martin.catty.fr/blog/inserer-un-tableau-dans-le-dom-avec-internet-explorer-7/" rel="alternate"/>
    <updated>2010-10-20T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Le problème&lt;/h2&gt;
&lt;p&gt;Avec prototype, l'insertion d'un tableau sous Internet Explorer 7 (et 6) dans le
&lt;acronym title="Document Object Model"&gt;DOM&lt;/acronym&gt; de la manière ci-dessous ne
fonctionne pas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"dom:loaded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tr"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"td"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"td"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;La solution&lt;/h2&gt;
&lt;p&gt;En effet il est nécessaire de spécifier explicitement les éléments thead et tbody:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"dom:loaded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;thead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"thead"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tr"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"td"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"td"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Selon le &lt;a href="http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-39872903"&gt;w3c&lt;/a&gt;
la création de ces éléments n'a pas à être explicite. Il s'agit donc (encore) d'un bug d'Internet Explorer, fixé sur IE8.&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Les technologies derrière synbioz.com</title>
    <id>https://martin.catty.fr/blog/les-technologies-derriere-synbioz.com/</id>
    <link href="https://martin.catty.fr/blog/les-technologies-derriere-synbioz.com/" rel="alternate"/>
    <updated>2010-10-12T00:00:00Z</updated>
    <author><name>Martin Catty</name><email>martin@catty.fr</email></author>
    <content type="html">&lt;h2&gt;Le site&lt;/h2&gt;
&lt;p&gt;Pour le site nous avons fait le choix du minimalisme. Il était très tentant de
partir sur Ruby on Rails 3, mais nous n'avions pas besoin de toutes ses
fonctionnalités.&lt;/p&gt;
&lt;p&gt;Nous nous sommes ensuite penchés sur &lt;a href="http://github.com/mojombo/jekyll" title="Accéder au lien github de jekyll"&gt;jekyll&lt;/a&gt;
qui permet de convertir des fichiers statiques en page HTML. Mais nous souhaitions
conserver un minimum de dynamisme.&lt;/p&gt;
&lt;p&gt;Nous avons donc fait le choix de &lt;a href="http://www.sinatrarb.com/" title="Accéder au site de sinatra"&gt;sinatra&lt;/a&gt;,
qui remplit complètement ces pré-requis.&lt;/p&gt;
&lt;p&gt;Pour les vues nous utilisons &lt;a href="http://haml-lang.com/"&gt;haml&lt;/a&gt; ainsi que &lt;a href="http://sass-lang.com/"&gt;sass&lt;/a&gt; pour les feuilles de style.&lt;/p&gt;
&lt;p&gt;Nous sommes aussi parti sur HTML5 &lt;strike&gt;parce que son doctype est plus simple à retenir&lt;/strike&gt; pour
commencer à utiliser les éléments implémentés par les navigateurs comme la validation de formulaire côté
client avec les nouveaux types d'input (email…) ainsi que les nouveaux attributs (required…).&lt;/p&gt;
&lt;p&gt;Concernant CSS3 nous utilisons ses propriétés dans une logique d'amélioration progressive.
Nous avons utilisé les media queries et la propriété -webkit-transform-3d (Safari uniquement) pour mettre en place
l'effet cube sur la page &lt;a href="http://www.synbioz.com/realisations" title="Accéder aux réalisations de Synbioz"&gt;réalisations&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Le blog&lt;/h2&gt;
&lt;p&gt;Pour le blog nous aurions pu passer par des solutions dédiées en Ruby on Rails comme &lt;a href="http://typosphere.org/" title="Accéder au site de typo"&gt;typo&lt;/a&gt;
mais nos besoins étaient trop modestes pour cela.&lt;/p&gt;
&lt;p&gt;De plus nous souhaitions nous baser sur git pour versionner nos billets, afin de pouvoir les rédiger depuis un simple
shell.&lt;/p&gt;
&lt;p&gt;Dans cette optique nous avons développé notre solution maison. L'idée est simple, nous avons un dépôt git avec deux branches, drafts et master.
Lors de l'écriture d'un article nous travaillons dans la branche drafts. Une fois le billet terminé il est rapatrié dans le
master avec git cherry-pick, puis pushé.&lt;/p&gt;
&lt;p&gt;Chaque article correspond donc à un simple fichier. Pour gérer les tags, l'auteur ou encore les catégories nous avons
repris un principe en place dans jekyll, le YAML Front Matter, c'est à dire que les informations relatives au billet
sont stockées dans l'entête du fichier et parsées à l'import. Exemple:&lt;/p&gt;
&lt;pre&gt;
---
contributors: ["Martin Catty"]
title: Foo
tags:
  - sinatra
  - thor
  - fr
---
&lt;/pre&gt;

&lt;p&gt;Sur le serveur une tâche &lt;a href="http://github.com/wycats/thor/"&gt;thor&lt;/a&gt; se charge de
de récupérer les fichiers écrits en Markdown dans la branche master, de les
convertir en html, de récupérer les entêtes et de les importer en base de donnée.&lt;/p&gt;
&lt;p&gt;Côté BDD c'est aussi le minimalisme qui prévaut avec sqlite3 et datamapper en guise d'&lt;acronym title="object-relational mapping"&gt;ORM&lt;/acronym&gt;.&lt;/p&gt;
&lt;h3&gt;Améliorations possibles&lt;/h3&gt;
&lt;p&gt;Pour le moment les articles sont automatiquement chargés par un cron job mais il serait plus efficace d'utiliser
le hook post-receive du dépôt pour invoquer dynamiquement la tâche thor.&lt;/p&gt;</content>
  </entry>
</feed>
