<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Nando Vieira</title>
    <description>Artigos sobre Ruby, Rails, JavaScript, jQuery, Node.js e muito mais. Por Nando Vieira.
</description>
    <language>pt-BR</language>
    <link>https://nandovieira.com.br</link>
    <atom:link href="https://nandovieira.com.br/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 28 Dec 2022 12:42:00 -0800</pubDate>
    <lastBuildDate>Wed, 28 Dec 2022 12:42:00 -0800</lastBuildDate>
    <item>
      <title>Usando 1password-cli para evitar segredos no perfil de seu terminal</title>
      <description>
        <![CDATA[<p>Uma coisa que sempre me incomodou na configuração de terminal foi adicionar
segredos como <code>export GITHUB_TOKEN=&lt;SECRET&gt;</code>. É ainda mais chato quando você tem
mais de um computador e precisa sincronizar segredos entre eles.</p>

<p>Eu finalmente decidi lidar com esse problema e para isso vou testar o
<a href="https://1password.com/downloads/command-line/">1password-cli</a>, um utilitário de linha de comando que permite
interagir com o <a href="https://1password.com/">1Password</a>.</p>

<p>No contexto deste artigo, vou pular a configuração do 1password-cli e vou
assumir que o comando <code>op</code> está disponível em seu terminal.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>which op
<span class="go">/Users/fnando/local/bin/op
</span></code></pre></div>
<p>O primeiro passo é fazer login com o comando <code>op signin</code>. Quando você terminar,
pode listar os seus cofres com o comando <code>op vault ls</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op vault <span class="nb">ls</span>
<span class="go">ID                            NAME
zyxlrxyu53kax7qqrxz554fgbq    Private
oblww37jzanxpl4wruzjz43i71    dev
</span></code></pre></div>
<p>No meu caso vou usar o cofre <code>dev</code> para armazenar os segredos que uso em
desenvolvimento. Você pode criar o item usando a linha de comando ou a interface
do 1Password. Eu vou usar o tipo <code>server</code> para isso:</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op item create <span class="nt">--title</span> main <span class="nt">--category</span> server <span class="nt">--vault</span> dev
<span class="go">ID:          kbfa7n6hcziy6ck7ys63pzta3a
Title:       main
Vault:       dev (oulww37jzinxpl4wruzjz43i44)
Created:     now
Updated:     now
Favorite:    false
Version:     0
Category:    SERVER
Fields:
  Admin Console:

  Hosting Provider:

</span></code></pre></div>
<p>Eu recomendo que você abra o 1Password e remova os itens que são adicionados por
padrão; infelizmente, eu não achei uma maneira de remover esses itens usando a
linha de comando. Aproveite para remover também os campos que foram adicionados
por padrão, já que eles não serão usados (senha, usuário e url).</p>

<p>Agora você pode adicionar os segredos usando a o comando <code>op item edit</code>. Se
preferir, você também pode usar a interface do 1Password.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op item edit <span class="nt">--vault</span> dev main <span class="s1">'dev.SOME_API_KEY[password]=SOME_KEY'</span> <span class="s1">'dev.ANOTHER_API_KEY[password]=SOME_OTHER_KEY'</span>
<span class="go">ID:          kbfa7n6hcziy6ck7ys63pzta3a
Title:       main
Vault:       dev (oulww37jzinxpl4wruzjz43i44)
Created:     40 seconds ago
Updated:     now by Nando Vieira
Favorite:    false
Version:     2
Category:    SERVER
Fields:
  dev:
    SOME_API_KEY:       SOME_KEY
    ANOTHER_API_KEY:    SOME_OTHER_KEY
</span></code></pre></div>
<p>Os segredos acima serão adicionados na seção chamada <code>dev</code>. Aqui tem um
screenshot mostrando os segredos no aplicativo do 1Password.</p>

<p><img src="https://nandovieira.s3.us-east-1.amazonaws.com/media/1password-cli-entries.jpg" alt="Screenshot do 1Password mostrando os itens que foram criados"></p>

<p>Depois que todos os segredos foram adicionados, podemos pegar esses itens em
formato JSON usando o comando <code>op item get</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op item get <span class="nt">--vault</span> dev main <span class="nt">--format</span> json
<span class="go">{
  "id": "kbfa7n6hcziy6ck7ys63pzta3a",
  "title": "main",
  "version": 3,
  "vault": {
    "id": "oulww37jzinxpl4wruzjz43i44",
    "name": "dev"
  },
  "category": "SERVER",
  "last_edited_by": "XCFD5SQIJNCZZOF2PBC7XED6QE",
  "created_at": "2022-12-28T08:44:55Z",
  "updated_at": "2022-12-28T00:45:35.033579-08:00",
  "sections": [
    {
      "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
      "label": "dev"
    }
  ],
  "fields": [
    {
      "id": "notesPlain",
      "type": "STRING",
      "purpose": "NOTES",
      "label": "notesPlain",
      "reference": "op://dev/main/notesPlain"
    },
    {
      "id": "xzuhdgcumcnz5peyvjckq2z4gi",
      "section": {
        "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
        "label": "dev"
      },
      "type": "CONCEALED",
      "label": "SOME_API_KEY",
      "value": "SOME_KEY",
      "reference": "op://dev/main/dev/SOME_API_KEY"
    },
    {
      "id": "4mg4ejw7q3pnsyworyiflofemq",
      "section": {
        "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
        "label": "dev"
      },
      "type": "CONCEALED",
      "label": "ANOTHER_API_KEY",
      "value": "SOME_OTHER_KEY",
      "reference": "op://dev/main/dev/ANOTHER_API_KEY"
    }
  ]
}
</span></code></pre></div>
<p>Note que o 1Password sempre retornará o campo <code>notesPlain</code>, já que ele é nativo
e não pode sequer ser removido.</p>

<p>O próximo passo é usar essa lista e convertê-la em um formato que seu terminal
entenda. Eu vou usar o <a href="https://stedolan.github.io/jq/">jq</a> para essa tarefa.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op item get main <span class="nt">--vault</span> dev <span class="nt">--format</span> json | jq <span class="nt">-r</span> <span class="s1">'.fields | map(select(has("value"))) | map("export " + .label + "=\"" + .value + "\"") | join("\n")'</span>
<span class="go">export SOME_API_KEY="SOME_KEY"
export ANOTHER_API_KEY="SOME_OTHER_KEY"
</span></code></pre></div>
<p>Você pode usar o <code>eval</code> com a saída acima, ou pode mandar a saída para um
arquivo que pode ser carregado pelo seu terminal. Eu vou usar a segunda opção,
mas antes vamos fazer uma limpa e criar um script executável. Vou chamar esse
script de <code>op-env</code>.</p>
<div class="highlight"><pre class="highlight shell"><code><span class="c">#!/usr/bin/env bash</span>

<span class="nb">set</span> <span class="nt">-e</span>

op item get main <span class="nt">--vault</span> dev <span class="nt">--format</span> json | jq <span class="nt">-r</span> <span class="s1">'.fields | map(select(has("value"))) | map("export " + .label + "=\"" + .value + "\"") | join("\n")'</span>
</code></pre></div>
<p>Torne esse script executável com o comando <code>chmod +x op-env</code>. Você poderá ver os
exports na tela se rodar o comando <code>op-env</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op-env
<span class="go">export SOME_API_KEY="SOME_KEY"
export ANOTHER_API_KEY="SOME_OTHER_KEY"
</span></code></pre></div>
<p>Agora basta adicionar as linhas seguintes ao seu perfil de terminal. Se você usa
<a href="https://www.zsh.org">ZSH</a>, pode usar o arquivo <code>~/.zshrc</code>. No <a href="https://www.gnu.org/software/bash/">Bash</a> você pode usar o
arquivo <code>~/.bashrc</code>.</p>
<div class="highlight"><pre class="highlight shell"><code><span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-f</span> ~/.op-env <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span>op run <span class="nt">--</span> <span class="nb">true
  </span>op-env <span class="o">&gt;</span> ~/.op-env
  <span class="nb">chmod </span>600 ~/.op-env
<span class="k">fi

</span><span class="nb">source</span> ~/.op-env
</code></pre></div>
<p>O código acima irá criar um arquivo chamado <code>~/.op-env</code> a menos que um já
exista, permitindo que os segredos fiquem em cache até que este arquivo seja
removido ou que você faça a atualização com o comando <code>op-env &gt; ~/.op-env</code>. Essa
etapa é necessária porque o 1Password pede autorização primeira chamada do
comando <code>op</code> em uma seção de terminal, ou seja, se você abrir uma nova aba, terá
que reautorizar o acesso do comando <code>op</code>. Ao fazer cache dos resultados, você
precisar fazer isso apenas de vez em quando.</p>

<h2>Finalizando</h2>

<p>É isso! O 1password-cli permite remover todos os segredos do seu perfil de
terminal, sem falar que faz o compartilhamento de segredos uma tarefa muito mais
fácil.</p>

<p>E por falar em 1password-cli, aproveite para dar uma olhada no <a href="https://developer.1password.com/docs/cli/secrets-environment-variables/">suporte de
arquivos <code>.env</code></a>, que permite o compartilhamento de segredos
entre as pessoas do seu time.</p>

<p>Como você lida com este problema? Você usa alguma outra técnica para isso?
Compartilhe sua solução nos comentários abaixo.</p>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p>Uma coisa que sempre me incomodou na configuração de terminal foi adicionar
segredos como <code>export GITHUB_TOKEN=&lt;SECRET&gt;</code>. É ainda mais chato quando você tem
mais de um computador e precisa sincronizar segredos entre eles.</p>

<p>Eu finalmente decidi lidar com esse problema e para isso vou testar o
<a href="https://1password.com/downloads/command-line/">1password-cli</a>, um utilitário de linha de comando que permite
interagir com o <a href="https://1password.com/">1Password</a>.</p>

<p>No contexto deste artigo, vou pular a configuração do 1password-cli e vou
assumir que o comando <code>op</code> está disponível em seu terminal.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>which op
<span class="go">/Users/fnando/local/bin/op
</span></code></pre></div>
<p>O primeiro passo é fazer login com o comando <code>op signin</code>. Quando você terminar,
pode listar os seus cofres com o comando <code>op vault ls</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op vault <span class="nb">ls</span>
<span class="go">ID                            NAME
zyxlrxyu53kax7qqrxz554fgbq    Private
oblww37jzanxpl4wruzjz43i71    dev
</span></code></pre></div>
<p>No meu caso vou usar o cofre <code>dev</code> para armazenar os segredos que uso em
desenvolvimento. Você pode criar o item usando a linha de comando ou a interface
do 1Password. Eu vou usar o tipo <code>server</code> para isso:</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op item create <span class="nt">--title</span> main <span class="nt">--category</span> server <span class="nt">--vault</span> dev
<span class="go">ID:          kbfa7n6hcziy6ck7ys63pzta3a
Title:       main
Vault:       dev (oulww37jzinxpl4wruzjz43i44)
Created:     now
Updated:     now
Favorite:    false
Version:     0
Category:    SERVER
Fields:
  Admin Console:

  Hosting Provider:

</span></code></pre></div>
<p>Eu recomendo que você abra o 1Password e remova os itens que são adicionados por
padrão; infelizmente, eu não achei uma maneira de remover esses itens usando a
linha de comando. Aproveite para remover também os campos que foram adicionados
por padrão, já que eles não serão usados (senha, usuário e url).</p>

<p>Agora você pode adicionar os segredos usando a o comando <code>op item edit</code>. Se
preferir, você também pode usar a interface do 1Password.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op item edit <span class="nt">--vault</span> dev main <span class="s1">'dev.SOME_API_KEY[password]=SOME_KEY'</span> <span class="s1">'dev.ANOTHER_API_KEY[password]=SOME_OTHER_KEY'</span>
<span class="go">ID:          kbfa7n6hcziy6ck7ys63pzta3a
Title:       main
Vault:       dev (oulww37jzinxpl4wruzjz43i44)
Created:     40 seconds ago
Updated:     now by Nando Vieira
Favorite:    false
Version:     2
Category:    SERVER
Fields:
  dev:
    SOME_API_KEY:       SOME_KEY
    ANOTHER_API_KEY:    SOME_OTHER_KEY
</span></code></pre></div>
<p>Os segredos acima serão adicionados na seção chamada <code>dev</code>. Aqui tem um
screenshot mostrando os segredos no aplicativo do 1Password.</p>

<p><img src="https://nandovieira.s3.us-east-1.amazonaws.com/media/1password-cli-entries.jpg" alt="Screenshot do 1Password mostrando os itens que foram criados"></p>

<p>Depois que todos os segredos foram adicionados, podemos pegar esses itens em
formato JSON usando o comando <code>op item get</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op item get <span class="nt">--vault</span> dev main <span class="nt">--format</span> json
<span class="go">{
  "id": "kbfa7n6hcziy6ck7ys63pzta3a",
  "title": "main",
  "version": 3,
  "vault": {
    "id": "oulww37jzinxpl4wruzjz43i44",
    "name": "dev"
  },
  "category": "SERVER",
  "last_edited_by": "XCFD5SQIJNCZZOF2PBC7XED6QE",
  "created_at": "2022-12-28T08:44:55Z",
  "updated_at": "2022-12-28T00:45:35.033579-08:00",
  "sections": [
    {
      "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
      "label": "dev"
    }
  ],
  "fields": [
    {
      "id": "notesPlain",
      "type": "STRING",
      "purpose": "NOTES",
      "label": "notesPlain",
      "reference": "op://dev/main/notesPlain"
    },
    {
      "id": "xzuhdgcumcnz5peyvjckq2z4gi",
      "section": {
        "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
        "label": "dev"
      },
      "type": "CONCEALED",
      "label": "SOME_API_KEY",
      "value": "SOME_KEY",
      "reference": "op://dev/main/dev/SOME_API_KEY"
    },
    {
      "id": "4mg4ejw7q3pnsyworyiflofemq",
      "section": {
        "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
        "label": "dev"
      },
      "type": "CONCEALED",
      "label": "ANOTHER_API_KEY",
      "value": "SOME_OTHER_KEY",
      "reference": "op://dev/main/dev/ANOTHER_API_KEY"
    }
  ]
}
</span></code></pre></div>
<p>Note que o 1Password sempre retornará o campo <code>notesPlain</code>, já que ele é nativo
e não pode sequer ser removido.</p>

<p>O próximo passo é usar essa lista e convertê-la em um formato que seu terminal
entenda. Eu vou usar o <a href="https://stedolan.github.io/jq/">jq</a> para essa tarefa.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op item get main <span class="nt">--vault</span> dev <span class="nt">--format</span> json | jq <span class="nt">-r</span> <span class="s1">'.fields | map(select(has("value"))) | map("export " + .label + "=\"" + .value + "\"") | join("\n")'</span>
<span class="go">export SOME_API_KEY="SOME_KEY"
export ANOTHER_API_KEY="SOME_OTHER_KEY"
</span></code></pre></div>
<p>Você pode usar o <code>eval</code> com a saída acima, ou pode mandar a saída para um
arquivo que pode ser carregado pelo seu terminal. Eu vou usar a segunda opção,
mas antes vamos fazer uma limpa e criar um script executável. Vou chamar esse
script de <code>op-env</code>.</p>
<div class="highlight"><pre class="highlight shell"><code><span class="c">#!/usr/bin/env bash</span>

<span class="nb">set</span> <span class="nt">-e</span>

op item get main <span class="nt">--vault</span> dev <span class="nt">--format</span> json | jq <span class="nt">-r</span> <span class="s1">'.fields | map(select(has("value"))) | map("export " + .label + "=\"" + .value + "\"") | join("\n")'</span>
</code></pre></div>
<p>Torne esse script executável com o comando <code>chmod +x op-env</code>. Você poderá ver os
exports na tela se rodar o comando <code>op-env</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>op-env
<span class="go">export SOME_API_KEY="SOME_KEY"
export ANOTHER_API_KEY="SOME_OTHER_KEY"
</span></code></pre></div>
<p>Agora basta adicionar as linhas seguintes ao seu perfil de terminal. Se você usa
<a href="https://www.zsh.org">ZSH</a>, pode usar o arquivo <code>~/.zshrc</code>. No <a href="https://www.gnu.org/software/bash/">Bash</a> você pode usar o
arquivo <code>~/.bashrc</code>.</p>
<div class="highlight"><pre class="highlight shell"><code><span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-f</span> ~/.op-env <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span>op run <span class="nt">--</span> <span class="nb">true
  </span>op-env <span class="o">&gt;</span> ~/.op-env
  <span class="nb">chmod </span>600 ~/.op-env
<span class="k">fi

</span><span class="nb">source</span> ~/.op-env
</code></pre></div>
<p>O código acima irá criar um arquivo chamado <code>~/.op-env</code> a menos que um já
exista, permitindo que os segredos fiquem em cache até que este arquivo seja
removido ou que você faça a atualização com o comando <code>op-env &gt; ~/.op-env</code>. Essa
etapa é necessária porque o 1Password pede autorização primeira chamada do
comando <code>op</code> em uma seção de terminal, ou seja, se você abrir uma nova aba, terá
que reautorizar o acesso do comando <code>op</code>. Ao fazer cache dos resultados, você
precisar fazer isso apenas de vez em quando.</p>

<h2>Finalizando</h2>

<p>É isso! O 1password-cli permite remover todos os segredos do seu perfil de
terminal, sem falar que faz o compartilhamento de segredos uma tarefa muito mais
fácil.</p>

<p>E por falar em 1password-cli, aproveite para dar uma olhada no <a href="https://developer.1password.com/docs/cli/secrets-environment-variables/">suporte de
arquivos <code>.env</code></a>, que permite o compartilhamento de segredos
entre as pessoas do seu time.</p>

<p>Como você lida com este problema? Você usa alguma outra técnica para isso?
Compartilhe sua solução nos comentários abaixo.</p>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/usando-1password-cli-para-evitar-segredos-no-perfil-de-seu-terminal</link>
      <guid>https://nandovieira.com.br/usando-1password-cli-para-evitar-segredos-no-perfil-de-seu-terminal</guid>
      <pubDate>Wed, 28 Dec 2022 12:42:00 -0800</pubDate>
    </item>
    <item>
      <title>Live Coding de Ruby on Rails</title>
      <description>
        <![CDATA[<p>Há um mês comecei uma <a href="https://www.youtube.com/watch?v=w2m9rtVmk7g&list=PLW1czhOfAtdeOe3J_AsOR2axtP6Z0E_sb">série de live coding no Youtube</a> onde estou
criando um app do zero, usando a última versão do Rails.</p>

<p>A ideia é fazer algo estilo Flickr/Instagram, com um monte de funcionalidades. O
projeto tem o nome de Photomatic e o <a href="https://github.com/fnando/photomatic">código-fonte está disponível integralmente
no Github</a>.</p>

<p class="yt-embed">
  <iframe width="720" height="405" src="https://www.youtube-nocookie.com/embed/w2m9rtVmk7g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</p>

<p>O projeto é ambicioso e se tudo der certo (i.e. eu não perder o gás), muita
coisa vai acontecer, como você pode ver no mindmap abaixo:</p>

<p><a href="https://nandovieira.s3.amazonaws.com/media/photomatic/mindmap.png"><img src="https://nandovieira.s3.amazonaws.com/media/photomatic/mindmap.png" alt="Mindmap com possíveis ideias"></a></p>

<p>Até agora fiz quatro sessões com um total de mais de 12h de live coding,
cobrindo o setup do projeto com Ruby on Rails 7, a configuração do frontend e
implementação do login por email. A ideia é fazer uma sessão semanal, mas vamos
como fica no futuro.</p>

<p>Para ver os vídeos anteriores, acesse a <a href="https://www.youtube.com/watch?v=w2m9rtVmk7g&list=PLW1czhOfAtdeOe3J_AsOR2axtP6Z0E_sb">playlist</a> com todos os vídeos
dessa série. Se quiser acompanhar os próximos streams ao vivo, , se inscreve no
canal em <a href="https://youtube.com/fnando">https://youtube.com/fnando</a> ou <a href="https://twitter.com/fnando">me segue no Twitter</a>.</p>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p>Há um mês comecei uma <a href="https://www.youtube.com/watch?v=w2m9rtVmk7g&list=PLW1czhOfAtdeOe3J_AsOR2axtP6Z0E_sb">série de live coding no Youtube</a> onde estou
criando um app do zero, usando a última versão do Rails.</p>

<p>A ideia é fazer algo estilo Flickr/Instagram, com um monte de funcionalidades. O
projeto tem o nome de Photomatic e o <a href="https://github.com/fnando/photomatic">código-fonte está disponível integralmente
no Github</a>.</p>

<p class="yt-embed">
  <iframe width="720" height="405" src="https://www.youtube-nocookie.com/embed/w2m9rtVmk7g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</p>

<p>O projeto é ambicioso e se tudo der certo (i.e. eu não perder o gás), muita
coisa vai acontecer, como você pode ver no mindmap abaixo:</p>

<p><a href="https://nandovieira.s3.amazonaws.com/media/photomatic/mindmap.png"><img src="https://nandovieira.s3.amazonaws.com/media/photomatic/mindmap.png" alt="Mindmap com possíveis ideias"></a></p>

<p>Até agora fiz quatro sessões com um total de mais de 12h de live coding,
cobrindo o setup do projeto com Ruby on Rails 7, a configuração do frontend e
implementação do login por email. A ideia é fazer uma sessão semanal, mas vamos
como fica no futuro.</p>

<p>Para ver os vídeos anteriores, acesse a <a href="https://www.youtube.com/watch?v=w2m9rtVmk7g&list=PLW1czhOfAtdeOe3J_AsOR2axtP6Z0E_sb">playlist</a> com todos os vídeos
dessa série. Se quiser acompanhar os próximos streams ao vivo, , se inscreve no
canal em <a href="https://youtube.com/fnando">https://youtube.com/fnando</a> ou <a href="https://twitter.com/fnando">me segue no Twitter</a>.</p>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/live-coding-de-ruby-on-rails</link>
      <guid>https://nandovieira.com.br/live-coding-de-ruby-on-rails</guid>
      <pubDate>Thu, 23 Dec 2021 10:42:00 -0700</pubDate>
    </item>
    <item>
      <title>Usando Let&amp;#39;s Encrypt em desenvolvimento com NGINX e AWS Route 53</title>
      <description>
        <![CDATA[<p><img src="https://s3.amazonaws.com/nandovieira/media/letsencrypt/letsencrypt-logo.png" alt="Let's Encrypt" class="align-right transparent" /></p>

<p>É muito comum ver pessoas tentando configurar <a href="https://en.wikipedia.org/wiki/HTTPS">HTTPS</a> em modo de
desenvolvimento. Se você desenvolve a bastante tempo, já deve ter tentado fazer
isso com certificados auto-assinados, comprando seu próprio certificado e
mexendo em seu arquivo de hosts ou usando ferramentas como <a href="https://github.com/puma/puma-dev">puma-dev</a>.
Enquanto essas soluções funcionam até um certo nível, <a href="https://letsencrypt.org">Let&rsquo;s
Encrypt</a> mudou o jogo, pelo menos para mim.</p>

<p>Com Let&rsquo;s Encrypt e um provedor de DNS como <a href="https://aws.amazon.com/route53/">AWS Route 53</a>, você poderá
usar HTTPS com subdomínios sem ter que mexer no arquivo <code>/etc/hosts</code> toda vez
que precisar de um novo host, nem ter que instalar ferramentas que criam um DNS
resolver.</p>

<p>Eu vou focar no macOS, que é o meu ambiente de desenvolvimento, mas você pode
seguir as mesmas instruções para outros sistemas. Lembre-se apenas de instalar
as dependências de software de acordo com o seu ambiente.</p>

<h2>Configurando AWS Route 53</h2>

<p>No dashboard da AWS, escolha &ldquo;Route 53&rdquo;, listado em &ldquo;Networking &amp; Content
Delivery&rdquo;. Você também pode digitar &ldquo;route 53&rdquo; no campo de busca. Isso irá te
levar para o dashboard do AWS Route 53.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/opening-aws-route53.png" alt="AWS Console: Abrindo AWS Route 53"></p>

<p>Na barra lateral, clique em &ldquo;Hosted Zones&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-hosted-zones-link.png" alt="AWS Route 53: Hosted Zones"></p>

<p>Você vai precisar de um domínio, então aqui vão algumas opções:</p>

<ol>
<li>Use um domínio que você já tem mas que não está sendo usado (e.g.
<code>fnando.com</code>).</li>
<li>Use um subdomínio em um domínio que já existe e está sendo usado (e.g.
<code>dev.fnando.com</code>).</li>
<li>Compre um novo domínio, talvez <code>.dev</code>, que diz exatamente para o que você
está usando (e.g. <code>fnando.dev</code>).</li>
</ol>

<p>Eu decidi comprar <em>outro</em> domínio e fui com a opção 3. O domínio é menor,
especialmente quando você está usando subdomínios (<code>acme.dev.fnando.com</code> vs
<code>acme.fnando.dev</code>). Sem dizer que é muito mais legal! 🤓</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-new-hosted-zone.png" alt="AWS Route 53: Criando uma nova hosted zone"></p>

<p>Depois que você criar a hosted zone, você precisa configurar o seu domínio e
apontar seu DNS para o AWS Route 53. Os hosts que você precisará estão no
registro de tipo <code>NS</code>. Vá ao seu provider de domínio e configure-o com esses
hosts. Eu uso <a href="https://namecheap.com">Namecheap</a>, e é assim que você faz lá:</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/namecheap.png" alt="Namecheap dashboard: registros de DNS"></p>

<p>De volta ao AWS Route 53, crie dois registros do tipo <code>A</code>, apontando para seu
ambiente de desenvolvimento, nesse caso o endereço <code>127.0.0.1</code>.</p>

<p>O primeiro registro é <code>fnando.dev</code>. Clique em &ldquo;Create Record Set&rdquo;, escolha &ldquo;A -
IPv4 address&rdquo; como tipo de registro e defina <code>127.0.0.1</code> como o valor do
registro. Certifique-se de não digitar nada no nome; caso contrário, você
estaria apontando um subdomínio.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-loopback-appex.png" alt="AWS Route 53: Adicionando um registro do tipo A records"></p>

<p>O segundo registro cuidará dos subdomínios. Clique em &ldquo;Create Record Set&rdquo; outra
vez, escolha &ldquo;A - IPv4 address&rdquo;, mas desta vez use <code>*</code> como o nome do registro.
Você valor deve ser <code>127.0.0.1</code>, assim como fizemos anteriormente.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-loopback-wildcard.png" alt="AWS Route 53: Adicionando registros do tipo A"></p>

<p>Agora temos que esperar o domínio ser propagado, mas isso não deve demorar
muito. Você pode verificar se já foi propagado com comando <code>dig</code>, como no
exemplo abaixo.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>dig +short fnando.dev A
<span class="go">127.0.0.1

</span><span class="gp">$</span><span class="w"> </span>dig +short <span class="s1">'*.fnando.dev'</span> A
<span class="go">127.0.0.1
</span></code></pre></div>
<p>Enquanto você espera, é hora de configurar uma credencial da AWS restrita para
esse domínio. Antes de continuar, olhe para a url do seu navegador: você vai
precisa do id da hosted zone, então guarde esse id em algum lugar.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-zone-id.png" alt="AWS Route 53: Pegando o zone id"></p>

<p>Nós vamos precisar de um novo usuário e uma política de acesso. Isso pode ser
feito em <a href="https://console.aws.amazon.com/iam/home">AWS IAM</a>, então procure por esta opção no menu &ldquo;Services&rdquo;.</p>

<p>Na barra lateral, clique em &ldquo;Policies&rdquo;, e então &ldquo;Create Policy&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-new-policy.png" alt="AWS IAM: Criando uma nova política de acesso"></p>

<p>Use o JSON abaixo como sua política de acesso. Lembre-se de mudar <code>YOUR_ZONE_ID</code>
para a zone id que você guardou anteriormente.</p>
<div class="highlight"><pre class="highlight json"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"letsencrypt-mac policy"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"route53:ListHostedZones"</span><span class="p">,</span><span class="w"> </span><span class="s2">"route53:GetChange"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"*"</span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"route53:ChangeResourceRecordSets"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"arn:aws:route53:::hostedzone/YOUR_ZONE_ID"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<p>Clique em &ldquo;Review policy&rdquo;. Dê um nome e clique em &ldquo;Create policy&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-policy-name.png" alt="AWS IAM: Specifying the policy name and type"></p>

<p>Agora é hora de criar um novo usuário. Na barra lateral, clique em &ldquo;Users&rdquo;, e
então &ldquo;Add User&rdquo;. Dê um nome como <code>letsencrypt-mac</code> ou algo que descreva sua
máquina de desenvolvimento. Selecione a opção &ldquo;Programmatic Access&rdquo; em &ldquo;Access
type&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-add-user.png" alt="AWS IAM: Adicionando um novo usuário"></p>

<p>Clique em &ldquo;Next&rdquo;. Agora, você precisa selecionar a política de acesso que
criamos anteriormente; clique em &ldquo;Attach existing policies directly&rdquo; e procure
pela política, nesse caso <code>letsencrypt-mac</code>.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-attach-policy.png" alt="AWS IAM: Anexando uma política de acesso ao usuário"></p>

<p>Clique em &ldquo;Next: Tags&rdquo;, e então &ldquo;Next: Review&rdquo;. Finalmente, clique em &ldquo;Create
User&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-create-user.png" alt="AWS IAM: Criando o usuário"></p>

<p>Esse passo é extremamente importante: aqui são listadas as chaves de acesso que
esse usuário deverá usar, então guarde-as em algum lugar seguro, como seu
<a href="https://1password.com">gerenciador de senhas</a>.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-credentials.png" alt="AWS IAM: Credenciais"></p>

<p>No que diz respeito à AWS, você configurou tudo o que precisava. Agora é hora de
configurar o certbot, a command-line que permite interagir com o Let&rsquo;s Encrypt.</p>

<h2>Configurando <code>certbot</code></h2>

<p>No macOS, eu uso <a href="https://brew.sh">homebrew</a> para gerenciar pacotes. Para instalar o <a href="https://certbot.eff.org">certbot</a>,
execute o comando <code>brew install certbot</code>. Você pode encontrar instruções para
outros sistemas no <a href="https://certbot.eff.org">site do certbot</a>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>brew <span class="nb">install </span>certbot
<span class="gp">==&gt;</span><span class="w"> </span>Downloading https://homebrew.bintray.com/bottles/certbot-1.4.0.catalina.bottle.tar.gz
<span class="go">Already downloaded: /Users/fnando/Library/Caches/Homebrew/downloads/f25750b88db0e526ac7fce38587eade58ef847892744acd31091a7d8fa4cdda4--certbot-1.4.0.catalina.bottle.tar.gz
</span><span class="gp">==&gt;</span><span class="w"> </span>Pouring certbot-1.4.0.catalina.bottle.tar.gz
<span class="go">🍺  /usr/local/Cellar/certbot/1.4.0: 1,451 files, 12.5MB
</span></code></pre></div>
<p>Parar gerar certificados que são validados automaticamente pelo certbot usando o
DNS da AWS Route 53, precisamos de um plugin chamado
<a href="https://github.com/certbot/certbot/tree/master/certbot-dns-route53"><code>certbot-dns-route53</code></a>, que pode ser instalado com o <code>pip</code>
do Python.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>pip3 <span class="nb">install </span>certbot-dns-route53
<span class="c">...
</span><span class="go">Installing collected packages: setuptools, zope.interface, pycparser, cffi, six, cryptography, PyOpenSSL, josepy, pytz, pyrfc3339, urllib3, certifi, chardet, idna, requests, requests-toolbelt, acme, docutils, python-dateutil, jmespath, botocore, s3transfer, boto3, parsedatetime, configobj, ConfigArgParse, distro, zope.deprecation, zope.event, zope.hookable, zope.proxy, zope.deferredimport, zope.component, certbot, certbot-dns-route53
Successfully installed ConfigArgParse-1.2.3 PyOpenSSL-19.1.0 acme-1.4.0 boto3-1.13.16 botocore-1.16.16 certbot-1.4.0 certbot-dns-route53-1.4.0 certifi-2020.4.5.1 cffi-1.14.0 chardet-3.0.4 configobj-5.0.6 cryptography-2.9.2 distro-1.5.0 docutils-0.15.2 idna-2.9 jmespath-0.10.0 josepy-1.3.0 parsedatetime-2.5 pycparser-2.20 pyrfc3339-1.1 python-dateutil-2.8.1 pytz-2020.1 requests-2.23.0 requests-toolbelt-0.9.1 s3transfer-0.3.3 setuptools-46.4.0 six-1.15.0 urllib3-1.25.9 zope.component-4.6.1 zope.deferredimport-4.3.1 zope.deprecation-4.4.0 zope.event-4.4 zope.hookable-5.0.1 zope.interface-5.1.0 zope.proxy-4.3.5
</span></code></pre></div>
<p>Para saber se o certbot pode ver o plugin, execute o comando <code>certbot plugins</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>certbot plugins
<span class="go">
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* dns-route53
Description: Obtain certificates using a DNS TXT record (if you are using AWS
Route53 for DNS).
Interfaces: IAuthenticator, IPlugin
Entry point: dns-route53 =
certbot_dns_route53._internal.dns_route53:Authenticator

* standalone
Description: Spin up a temporary webserver
Interfaces: IAuthenticator, IPlugin
Entry point: standalone = certbot._internal.plugins.standalone:Authenticator

* webroot
Description: Place files in webroot directory
Interfaces: IAuthenticator, IPlugin
Entry point: webroot = certbot._internal.plugins.webroot:Authenticator
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
</span></code></pre></div>
<p>Agora chegou a hora de gerar os certificados. A primeira coisa a saber é que
você vai precisar exportar as credenciais da AWS como as variáveis de ambiente
<code>AWS_ACCESS_KEY_ID</code> e <code>AWS_SECRET_ACCESS_KEY</code>. Você decide como quer gerenciar
essas variáveis, mas eu gosto de adicioná-las ao arquivo <code>~/.zsh/user.sh</code>, que é
carregado pelo arquivo <code>~/.zshrc</code>. Para o propósito deste artigo, vou exportar
essas variáveis antes de usuá-las.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">AWS_ACCESS_KEY_ID</span><span class="o">=</span>AKIAY7V6RRKVYA3Z4MGA
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">AWS_SECRET_ACCESS_KEY</span><span class="o">=</span><span class="s1">'REDACTED_SECRET'</span>
</code></pre></div>
<p>Para gerar certificados, você vai precisar usar o comando <code>certbot certonly</code>.
Note que estamos definindo diretórios locais; isso permite que você execute o
comando se precisar de <code>sudo</code>. Depois que o processo estiver concluído, o
certificado será salvo em <code>~/local/letsencrypt/live/fnando.dev</code>. Se você tem
dúvidas se configurou tudo corretamente, use a opção <code>--dry-run</code>; isso executará
o certbot em seu ambiente de staging, que tem um limite mais para as falhas. Em
produção, você terá o acesso bloqueado por uma hora depois de um certo número de
tentativas.</p>
<div class="highlight"><pre class="highlight shell"><code>certbot certonly <span class="se">\</span>
<span class="nt">-n</span> <span class="se">\</span>
<span class="nt">--agree-tos</span> <span class="se">\</span>
<span class="nt">--email</span> user@example.com <span class="se">\</span>
<span class="nt">-d</span> fnando.dev <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'*.fnando.dev'</span> <span class="se">\</span>
<span class="nt">--dns-route53</span> <span class="se">\</span>
<span class="nt">--preferred-challenges</span><span class="o">=</span>dns <span class="se">\</span>
<span class="nt">--logs-dir</span> /tmp/letsencrypt <span class="se">\</span>
<span class="nt">--config-dir</span> ~/local/letsencrypt <span class="se">\</span>
<span class="nt">--work-dir</span> /tmp/letsencrypt
</code></pre></div>
<p>Quando a execução terminar, você verá algo como isso:</p>
<div class="highlight"><pre class="highlight console"><code><span class="go">Saving debug log to /tmp/letsencrypt/letsencrypt.log
Found credentials in environment variables.
Plugins selected: Authenticator dns-route53, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for fnando.dev
dns-01 challenge for fnando.dev
Waiting for verification...
Cleaning up challenges
Non-standard path(s), might not work with crontab installed by your operating system package manager

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /Users/fnando/local/letsencrypt/live/fnando.dev/fullchain.pem
   Your key file has been saved at:
   /Users/fnando/local/letsencrypt/live/fnando.dev/privkey.pem
   Your cert will expire on 2020-08-23. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
</span></code></pre></div>
<p>Pronto! Seus certificados foram gerados. Quando o certificado estiver para
expirar (os certificados tem validade de três meses), você receberá um email da
Let&rsquo;s Encrypt.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/letsencrypt-email.png" alt="Email de expiração da Let&#39;s Encrypt"></p>

<p>Para renovar o certificado, basta executar o mesmo comando acima (i.e.
<code>certbot certonly</code>).</p>

<h2>Configurando NGINX</h2>

<p>No macOS, podemos instalar o NGINX usando homebrew. Basta executar o comando
<code>brew install nginx</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>brew <span class="nb">install </span>nginx
<span class="gp">==&gt;</span><span class="w"> </span>Downloading https://homebrew.bintray.com/bottles/nginx-1.17.10.catalina.bottle.tar.gz
<span class="go">Already downloaded: /Users/fnando/Library/Caches/Homebrew/downloads/d7118d9cc53ef3be545ac049f7e50aa30f2378f673aa925702deaa6117fb403c--nginx-1.17.10.catalina.bottle.tar.gz
</span><span class="gp">==&gt;</span><span class="w"> </span>Pouring nginx-1.17.10.catalina.bottle.tar.gz
<span class="gp">==&gt;</span><span class="w"> </span>Caveats
<span class="go">Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
  brew services start nginx
Or, if you don't want/need a background service you can just run:
  nginx
</span><span class="gp">==&gt;</span><span class="w"> </span>Summary
<span class="go">🍺  /usr/local/Cellar/nginx/1.17.10: 25 files, 2.1MB
</span></code></pre></div>
<p>Como desenvolvo aplicações web constantemente, eu gosto de iniciar o NGINX
automaticamente. Para fazer isso, execute o comando
<code>sudo brew services start nginx</code>, e o homebrew se encarregará de copiar o
arquivo <code>/Library/LaunchDaemons/homebrew.mxcl.nginx.plist</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>brew services start nginx
<span class="go">Warning: Taking root:admin ownership of some nginx paths:
  /usr/local/Cellar/nginx/1.17.10/bin
  /usr/local/Cellar/nginx/1.17.10/bin/nginx
  /usr/local/opt/nginx
  /usr/local/opt/nginx/bin
  /usr/local/var/homebrew/linked/nginx
This will require manual removal of these paths using `sudo rm` on
brew upgrade/reinstall/uninstall.
Warning: nginx must be run as non-root to start at user login!
</span><span class="gp">==&gt;</span><span class="w"> </span>Successfully started <span class="sb">`</span>nginx<span class="sb">`</span> <span class="o">(</span>label: homebrew.mxcl.nginx<span class="o">)</span>
</code></pre></div>
<p>É, eu sei&hellip; <code>sudo</code>. Mas só assim você poderá acessar <code>https://fnando.dev</code> sem
ter que especificar uma porta. Outra alternativa é mudar a permissão do
diretório <code>/usr/local</code> para o grupo <code>admin</code>, então você decide.</p>

<p>A configuração do NGINX deve ser adicionada no diretório
<code>/usr/local/etc/nginx/servers/</code>. Eu gosto de usar o domínio apex (i.e. o domínio
sem nenhum subdomínio) como nome do arquivo, então nesse caso seria
<code>/usr/local/etc/nginx/servers/fnando.dev.conf</code>. Use o conteúdo abaixo.</p>
<div class="highlight"><pre class="highlight nginx"><code><span class="k">upstream</span> <span class="s">fnando_dev</span> <span class="p">{</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">3000</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">4567</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">5000</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">5001</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">9292</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">9393</span> <span class="s">max_fails=0</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">server</span> <span class="p">{</span>
  <span class="kn">listen</span>              <span class="mi">80</span><span class="p">;</span>
  <span class="kn">listen</span>              <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
  <span class="kn">server_name</span>         <span class="s">fnando.dev</span><span class="p">;</span>

  <span class="kn">ssl_certificate</span>     <span class="n">/Users/fnando/local/letsencrypt/live/fnando.dev/fullchain.pem</span><span class="p">;</span>
  <span class="kn">ssl_certificate_key</span> <span class="n">/Users/fnando/local/letsencrypt/live/fnando.dev/privkey.pem</span><span class="p">;</span>

  <span class="kn">ssl_protocols</span>       <span class="s">TLSv1</span> <span class="s">TLSv1.1</span> <span class="s">TLSv1.2</span><span class="p">;</span>
  <span class="kn">ssl_ciphers</span>         <span class="s">HIGH:!aNULL:!MD5</span><span class="p">;</span>

  <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
    <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$http_host</span><span class="p">;</span>
    <span class="kn">proxy_redirect</span> <span class="no">off</span><span class="p">;</span>
    <span class="kn">proxy_pass</span> <span class="s">http://fnando_dev</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">server</span> <span class="p">{</span>
  <span class="kn">listen</span>              <span class="mi">80</span><span class="p">;</span>
  <span class="kn">listen</span>              <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
  <span class="kn">server_name</span>         <span class="s">*.fnando.dev</span><span class="p">;</span>

  <span class="kn">ssl_certificate</span>     <span class="n">/Users/fnando/local/letsencrypt/live/fnando.dev/fullchain.pem</span><span class="p">;</span>
  <span class="kn">ssl_certificate_key</span> <span class="n">/Users/fnando/local/letsencrypt/live/fnando.dev/privkey.pem</span><span class="p">;</span>

  <span class="kn">ssl_protocols</span>       <span class="s">TLSv1</span> <span class="s">TLSv1.1</span> <span class="s">TLSv1.2</span><span class="p">;</span>
  <span class="kn">ssl_ciphers</span>         <span class="s">HIGH:!aNULL:!MD5</span><span class="p">;</span>

  <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
    <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$http_host</span><span class="p">;</span>
    <span class="kn">proxy_redirect</span> <span class="no">off</span><span class="p">;</span>
    <span class="kn">proxy_pass</span> <span class="s">http://fnando_dev</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Note que você não pode usar <code>~</code> para indicar o diretório home, então use o
caminho completo. Outra coisa que você precisa fazer é especificar as portas que
as aplicações web podem rodar no bloco <a href="https://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream">upstream</a>, então adicione a porta dos
frameworks que você usa ali.</p>

<p>Agora, reinicie o NGINX com o comando <code>sudo brew services restart nginx</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code>$ brew services restart nginx
Stopping `nginx`... (might take a while)
==&gt; Successfully stopped `nginx` (label: homebrew.mxcl.nginx)
==&gt; Successfully started `nginx` (label: homebrew.mxcl.nginx)
</code></pre></div>
<p>Se tudo deu certo, você já pode acessar sua aplicação web usando um domínio com
HTTPS, como <code>https://fnando.dev</code>. Para testar, inicie sua aplicação e visite
essa url.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/safari.png" alt="Exemplo de aplicação web rodando com HTTPS"></p>

<p>Como você pode ver, esse é um certificado 100% válido.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/safari-certificate.png" alt="Informações sobre o certificado no Safari"></p>

<p>E subdomínios funcionam sem problema algum, e sem precisar que você mexa no seu
arquivo <code>/etc/hosts</code>. 😎</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/safari-subdomain.png" alt="Exemplo de aplicação web roando com subdomínios e HTTPS"></p>

<h2>Finalizando</h2>

<p>Você pode estar se perguntando por que desenvolver usando HTTPS, e a resposta é
simples: muitas coisas exigem HTTPS, como <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API">webauthn</a>. Você pode ver uma lista
completa de todas as funcionalidades que existem contexto seguro no <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts">site da
Mozilla</a>.</p>

<p>Eu já testei diversas combinações para fazer HTTPS funcionar em ambiente de
desenvolvimento, mas essa é de longe a melhor opção. Nenhum hack, nada de
adicionar certificados raíz na máquina. Obrigado, Let&rsquo;s Encrypt.</p>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p><img src="https://s3.amazonaws.com/nandovieira/media/letsencrypt/letsencrypt-logo.png" alt="Let's Encrypt" class="align-right transparent" /></p>

<p>É muito comum ver pessoas tentando configurar <a href="https://en.wikipedia.org/wiki/HTTPS">HTTPS</a> em modo de
desenvolvimento. Se você desenvolve a bastante tempo, já deve ter tentado fazer
isso com certificados auto-assinados, comprando seu próprio certificado e
mexendo em seu arquivo de hosts ou usando ferramentas como <a href="https://github.com/puma/puma-dev">puma-dev</a>.
Enquanto essas soluções funcionam até um certo nível, <a href="https://letsencrypt.org">Let&rsquo;s
Encrypt</a> mudou o jogo, pelo menos para mim.</p>

<p>Com Let&rsquo;s Encrypt e um provedor de DNS como <a href="https://aws.amazon.com/route53/">AWS Route 53</a>, você poderá
usar HTTPS com subdomínios sem ter que mexer no arquivo <code>/etc/hosts</code> toda vez
que precisar de um novo host, nem ter que instalar ferramentas que criam um DNS
resolver.</p>

<p>Eu vou focar no macOS, que é o meu ambiente de desenvolvimento, mas você pode
seguir as mesmas instruções para outros sistemas. Lembre-se apenas de instalar
as dependências de software de acordo com o seu ambiente.</p>

<h2>Configurando AWS Route 53</h2>

<p>No dashboard da AWS, escolha &ldquo;Route 53&rdquo;, listado em &ldquo;Networking &amp; Content
Delivery&rdquo;. Você também pode digitar &ldquo;route 53&rdquo; no campo de busca. Isso irá te
levar para o dashboard do AWS Route 53.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/opening-aws-route53.png" alt="AWS Console: Abrindo AWS Route 53"></p>

<p>Na barra lateral, clique em &ldquo;Hosted Zones&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-hosted-zones-link.png" alt="AWS Route 53: Hosted Zones"></p>

<p>Você vai precisar de um domínio, então aqui vão algumas opções:</p>

<ol>
<li>Use um domínio que você já tem mas que não está sendo usado (e.g.
<code>fnando.com</code>).</li>
<li>Use um subdomínio em um domínio que já existe e está sendo usado (e.g.
<code>dev.fnando.com</code>).</li>
<li>Compre um novo domínio, talvez <code>.dev</code>, que diz exatamente para o que você
está usando (e.g. <code>fnando.dev</code>).</li>
</ol>

<p>Eu decidi comprar <em>outro</em> domínio e fui com a opção 3. O domínio é menor,
especialmente quando você está usando subdomínios (<code>acme.dev.fnando.com</code> vs
<code>acme.fnando.dev</code>). Sem dizer que é muito mais legal! 🤓</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-new-hosted-zone.png" alt="AWS Route 53: Criando uma nova hosted zone"></p>

<p>Depois que você criar a hosted zone, você precisa configurar o seu domínio e
apontar seu DNS para o AWS Route 53. Os hosts que você precisará estão no
registro de tipo <code>NS</code>. Vá ao seu provider de domínio e configure-o com esses
hosts. Eu uso <a href="https://namecheap.com">Namecheap</a>, e é assim que você faz lá:</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/namecheap.png" alt="Namecheap dashboard: registros de DNS"></p>

<p>De volta ao AWS Route 53, crie dois registros do tipo <code>A</code>, apontando para seu
ambiente de desenvolvimento, nesse caso o endereço <code>127.0.0.1</code>.</p>

<p>O primeiro registro é <code>fnando.dev</code>. Clique em &ldquo;Create Record Set&rdquo;, escolha &ldquo;A -
IPv4 address&rdquo; como tipo de registro e defina <code>127.0.0.1</code> como o valor do
registro. Certifique-se de não digitar nada no nome; caso contrário, você
estaria apontando um subdomínio.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-loopback-appex.png" alt="AWS Route 53: Adicionando um registro do tipo A records"></p>

<p>O segundo registro cuidará dos subdomínios. Clique em &ldquo;Create Record Set&rdquo; outra
vez, escolha &ldquo;A - IPv4 address&rdquo;, mas desta vez use <code>*</code> como o nome do registro.
Você valor deve ser <code>127.0.0.1</code>, assim como fizemos anteriormente.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-loopback-wildcard.png" alt="AWS Route 53: Adicionando registros do tipo A"></p>

<p>Agora temos que esperar o domínio ser propagado, mas isso não deve demorar
muito. Você pode verificar se já foi propagado com comando <code>dig</code>, como no
exemplo abaixo.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>dig +short fnando.dev A
<span class="go">127.0.0.1

</span><span class="gp">$</span><span class="w"> </span>dig +short <span class="s1">'*.fnando.dev'</span> A
<span class="go">127.0.0.1
</span></code></pre></div>
<p>Enquanto você espera, é hora de configurar uma credencial da AWS restrita para
esse domínio. Antes de continuar, olhe para a url do seu navegador: você vai
precisa do id da hosted zone, então guarde esse id em algum lugar.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-route53-zone-id.png" alt="AWS Route 53: Pegando o zone id"></p>

<p>Nós vamos precisar de um novo usuário e uma política de acesso. Isso pode ser
feito em <a href="https://console.aws.amazon.com/iam/home">AWS IAM</a>, então procure por esta opção no menu &ldquo;Services&rdquo;.</p>

<p>Na barra lateral, clique em &ldquo;Policies&rdquo;, e então &ldquo;Create Policy&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-new-policy.png" alt="AWS IAM: Criando uma nova política de acesso"></p>

<p>Use o JSON abaixo como sua política de acesso. Lembre-se de mudar <code>YOUR_ZONE_ID</code>
para a zone id que você guardou anteriormente.</p>
<div class="highlight"><pre class="highlight json"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"letsencrypt-mac policy"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"route53:ListHostedZones"</span><span class="p">,</span><span class="w"> </span><span class="s2">"route53:GetChange"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"*"</span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"route53:ChangeResourceRecordSets"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"arn:aws:route53:::hostedzone/YOUR_ZONE_ID"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<p>Clique em &ldquo;Review policy&rdquo;. Dê um nome e clique em &ldquo;Create policy&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-policy-name.png" alt="AWS IAM: Specifying the policy name and type"></p>

<p>Agora é hora de criar um novo usuário. Na barra lateral, clique em &ldquo;Users&rdquo;, e
então &ldquo;Add User&rdquo;. Dê um nome como <code>letsencrypt-mac</code> ou algo que descreva sua
máquina de desenvolvimento. Selecione a opção &ldquo;Programmatic Access&rdquo; em &ldquo;Access
type&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-add-user.png" alt="AWS IAM: Adicionando um novo usuário"></p>

<p>Clique em &ldquo;Next&rdquo;. Agora, você precisa selecionar a política de acesso que
criamos anteriormente; clique em &ldquo;Attach existing policies directly&rdquo; e procure
pela política, nesse caso <code>letsencrypt-mac</code>.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-attach-policy.png" alt="AWS IAM: Anexando uma política de acesso ao usuário"></p>

<p>Clique em &ldquo;Next: Tags&rdquo;, e então &ldquo;Next: Review&rdquo;. Finalmente, clique em &ldquo;Create
User&rdquo;.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-create-user.png" alt="AWS IAM: Criando o usuário"></p>

<p>Esse passo é extremamente importante: aqui são listadas as chaves de acesso que
esse usuário deverá usar, então guarde-as em algum lugar seguro, como seu
<a href="https://1password.com">gerenciador de senhas</a>.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/aws-iam-credentials.png" alt="AWS IAM: Credenciais"></p>

<p>No que diz respeito à AWS, você configurou tudo o que precisava. Agora é hora de
configurar o certbot, a command-line que permite interagir com o Let&rsquo;s Encrypt.</p>

<h2>Configurando <code>certbot</code></h2>

<p>No macOS, eu uso <a href="https://brew.sh">homebrew</a> para gerenciar pacotes. Para instalar o <a href="https://certbot.eff.org">certbot</a>,
execute o comando <code>brew install certbot</code>. Você pode encontrar instruções para
outros sistemas no <a href="https://certbot.eff.org">site do certbot</a>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>brew <span class="nb">install </span>certbot
<span class="gp">==&gt;</span><span class="w"> </span>Downloading https://homebrew.bintray.com/bottles/certbot-1.4.0.catalina.bottle.tar.gz
<span class="go">Already downloaded: /Users/fnando/Library/Caches/Homebrew/downloads/f25750b88db0e526ac7fce38587eade58ef847892744acd31091a7d8fa4cdda4--certbot-1.4.0.catalina.bottle.tar.gz
</span><span class="gp">==&gt;</span><span class="w"> </span>Pouring certbot-1.4.0.catalina.bottle.tar.gz
<span class="go">🍺  /usr/local/Cellar/certbot/1.4.0: 1,451 files, 12.5MB
</span></code></pre></div>
<p>Parar gerar certificados que são validados automaticamente pelo certbot usando o
DNS da AWS Route 53, precisamos de um plugin chamado
<a href="https://github.com/certbot/certbot/tree/master/certbot-dns-route53"><code>certbot-dns-route53</code></a>, que pode ser instalado com o <code>pip</code>
do Python.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>pip3 <span class="nb">install </span>certbot-dns-route53
<span class="c">...
</span><span class="go">Installing collected packages: setuptools, zope.interface, pycparser, cffi, six, cryptography, PyOpenSSL, josepy, pytz, pyrfc3339, urllib3, certifi, chardet, idna, requests, requests-toolbelt, acme, docutils, python-dateutil, jmespath, botocore, s3transfer, boto3, parsedatetime, configobj, ConfigArgParse, distro, zope.deprecation, zope.event, zope.hookable, zope.proxy, zope.deferredimport, zope.component, certbot, certbot-dns-route53
Successfully installed ConfigArgParse-1.2.3 PyOpenSSL-19.1.0 acme-1.4.0 boto3-1.13.16 botocore-1.16.16 certbot-1.4.0 certbot-dns-route53-1.4.0 certifi-2020.4.5.1 cffi-1.14.0 chardet-3.0.4 configobj-5.0.6 cryptography-2.9.2 distro-1.5.0 docutils-0.15.2 idna-2.9 jmespath-0.10.0 josepy-1.3.0 parsedatetime-2.5 pycparser-2.20 pyrfc3339-1.1 python-dateutil-2.8.1 pytz-2020.1 requests-2.23.0 requests-toolbelt-0.9.1 s3transfer-0.3.3 setuptools-46.4.0 six-1.15.0 urllib3-1.25.9 zope.component-4.6.1 zope.deferredimport-4.3.1 zope.deprecation-4.4.0 zope.event-4.4 zope.hookable-5.0.1 zope.interface-5.1.0 zope.proxy-4.3.5
</span></code></pre></div>
<p>Para saber se o certbot pode ver o plugin, execute o comando <code>certbot plugins</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>certbot plugins
<span class="go">
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* dns-route53
Description: Obtain certificates using a DNS TXT record (if you are using AWS
Route53 for DNS).
Interfaces: IAuthenticator, IPlugin
Entry point: dns-route53 =
certbot_dns_route53._internal.dns_route53:Authenticator

* standalone
Description: Spin up a temporary webserver
Interfaces: IAuthenticator, IPlugin
Entry point: standalone = certbot._internal.plugins.standalone:Authenticator

* webroot
Description: Place files in webroot directory
Interfaces: IAuthenticator, IPlugin
Entry point: webroot = certbot._internal.plugins.webroot:Authenticator
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
</span></code></pre></div>
<p>Agora chegou a hora de gerar os certificados. A primeira coisa a saber é que
você vai precisar exportar as credenciais da AWS como as variáveis de ambiente
<code>AWS_ACCESS_KEY_ID</code> e <code>AWS_SECRET_ACCESS_KEY</code>. Você decide como quer gerenciar
essas variáveis, mas eu gosto de adicioná-las ao arquivo <code>~/.zsh/user.sh</code>, que é
carregado pelo arquivo <code>~/.zshrc</code>. Para o propósito deste artigo, vou exportar
essas variáveis antes de usuá-las.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">AWS_ACCESS_KEY_ID</span><span class="o">=</span>AKIAY7V6RRKVYA3Z4MGA
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">AWS_SECRET_ACCESS_KEY</span><span class="o">=</span><span class="s1">'REDACTED_SECRET'</span>
</code></pre></div>
<p>Para gerar certificados, você vai precisar usar o comando <code>certbot certonly</code>.
Note que estamos definindo diretórios locais; isso permite que você execute o
comando se precisar de <code>sudo</code>. Depois que o processo estiver concluído, o
certificado será salvo em <code>~/local/letsencrypt/live/fnando.dev</code>. Se você tem
dúvidas se configurou tudo corretamente, use a opção <code>--dry-run</code>; isso executará
o certbot em seu ambiente de staging, que tem um limite mais para as falhas. Em
produção, você terá o acesso bloqueado por uma hora depois de um certo número de
tentativas.</p>
<div class="highlight"><pre class="highlight shell"><code>certbot certonly <span class="se">\</span>
<span class="nt">-n</span> <span class="se">\</span>
<span class="nt">--agree-tos</span> <span class="se">\</span>
<span class="nt">--email</span> user@example.com <span class="se">\</span>
<span class="nt">-d</span> fnando.dev <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'*.fnando.dev'</span> <span class="se">\</span>
<span class="nt">--dns-route53</span> <span class="se">\</span>
<span class="nt">--preferred-challenges</span><span class="o">=</span>dns <span class="se">\</span>
<span class="nt">--logs-dir</span> /tmp/letsencrypt <span class="se">\</span>
<span class="nt">--config-dir</span> ~/local/letsencrypt <span class="se">\</span>
<span class="nt">--work-dir</span> /tmp/letsencrypt
</code></pre></div>
<p>Quando a execução terminar, você verá algo como isso:</p>
<div class="highlight"><pre class="highlight console"><code><span class="go">Saving debug log to /tmp/letsencrypt/letsencrypt.log
Found credentials in environment variables.
Plugins selected: Authenticator dns-route53, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for fnando.dev
dns-01 challenge for fnando.dev
Waiting for verification...
Cleaning up challenges
Non-standard path(s), might not work with crontab installed by your operating system package manager

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /Users/fnando/local/letsencrypt/live/fnando.dev/fullchain.pem
   Your key file has been saved at:
   /Users/fnando/local/letsencrypt/live/fnando.dev/privkey.pem
   Your cert will expire on 2020-08-23. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
</span></code></pre></div>
<p>Pronto! Seus certificados foram gerados. Quando o certificado estiver para
expirar (os certificados tem validade de três meses), você receberá um email da
Let&rsquo;s Encrypt.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/letsencrypt-email.png" alt="Email de expiração da Let&#39;s Encrypt"></p>

<p>Para renovar o certificado, basta executar o mesmo comando acima (i.e.
<code>certbot certonly</code>).</p>

<h2>Configurando NGINX</h2>

<p>No macOS, podemos instalar o NGINX usando homebrew. Basta executar o comando
<code>brew install nginx</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>brew <span class="nb">install </span>nginx
<span class="gp">==&gt;</span><span class="w"> </span>Downloading https://homebrew.bintray.com/bottles/nginx-1.17.10.catalina.bottle.tar.gz
<span class="go">Already downloaded: /Users/fnando/Library/Caches/Homebrew/downloads/d7118d9cc53ef3be545ac049f7e50aa30f2378f673aa925702deaa6117fb403c--nginx-1.17.10.catalina.bottle.tar.gz
</span><span class="gp">==&gt;</span><span class="w"> </span>Pouring nginx-1.17.10.catalina.bottle.tar.gz
<span class="gp">==&gt;</span><span class="w"> </span>Caveats
<span class="go">Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
  brew services start nginx
Or, if you don't want/need a background service you can just run:
  nginx
</span><span class="gp">==&gt;</span><span class="w"> </span>Summary
<span class="go">🍺  /usr/local/Cellar/nginx/1.17.10: 25 files, 2.1MB
</span></code></pre></div>
<p>Como desenvolvo aplicações web constantemente, eu gosto de iniciar o NGINX
automaticamente. Para fazer isso, execute o comando
<code>sudo brew services start nginx</code>, e o homebrew se encarregará de copiar o
arquivo <code>/Library/LaunchDaemons/homebrew.mxcl.nginx.plist</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>brew services start nginx
<span class="go">Warning: Taking root:admin ownership of some nginx paths:
  /usr/local/Cellar/nginx/1.17.10/bin
  /usr/local/Cellar/nginx/1.17.10/bin/nginx
  /usr/local/opt/nginx
  /usr/local/opt/nginx/bin
  /usr/local/var/homebrew/linked/nginx
This will require manual removal of these paths using `sudo rm` on
brew upgrade/reinstall/uninstall.
Warning: nginx must be run as non-root to start at user login!
</span><span class="gp">==&gt;</span><span class="w"> </span>Successfully started <span class="sb">`</span>nginx<span class="sb">`</span> <span class="o">(</span>label: homebrew.mxcl.nginx<span class="o">)</span>
</code></pre></div>
<p>É, eu sei&hellip; <code>sudo</code>. Mas só assim você poderá acessar <code>https://fnando.dev</code> sem
ter que especificar uma porta. Outra alternativa é mudar a permissão do
diretório <code>/usr/local</code> para o grupo <code>admin</code>, então você decide.</p>

<p>A configuração do NGINX deve ser adicionada no diretório
<code>/usr/local/etc/nginx/servers/</code>. Eu gosto de usar o domínio apex (i.e. o domínio
sem nenhum subdomínio) como nome do arquivo, então nesse caso seria
<code>/usr/local/etc/nginx/servers/fnando.dev.conf</code>. Use o conteúdo abaixo.</p>
<div class="highlight"><pre class="highlight nginx"><code><span class="k">upstream</span> <span class="s">fnando_dev</span> <span class="p">{</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">3000</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">4567</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">5000</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">5001</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">9292</span> <span class="s">max_fails=0</span><span class="p">;</span>
  <span class="kn">server</span> <span class="nf">127.0.0.1</span><span class="p">:</span><span class="mi">9393</span> <span class="s">max_fails=0</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">server</span> <span class="p">{</span>
  <span class="kn">listen</span>              <span class="mi">80</span><span class="p">;</span>
  <span class="kn">listen</span>              <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
  <span class="kn">server_name</span>         <span class="s">fnando.dev</span><span class="p">;</span>

  <span class="kn">ssl_certificate</span>     <span class="n">/Users/fnando/local/letsencrypt/live/fnando.dev/fullchain.pem</span><span class="p">;</span>
  <span class="kn">ssl_certificate_key</span> <span class="n">/Users/fnando/local/letsencrypt/live/fnando.dev/privkey.pem</span><span class="p">;</span>

  <span class="kn">ssl_protocols</span>       <span class="s">TLSv1</span> <span class="s">TLSv1.1</span> <span class="s">TLSv1.2</span><span class="p">;</span>
  <span class="kn">ssl_ciphers</span>         <span class="s">HIGH:!aNULL:!MD5</span><span class="p">;</span>

  <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
    <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$http_host</span><span class="p">;</span>
    <span class="kn">proxy_redirect</span> <span class="no">off</span><span class="p">;</span>
    <span class="kn">proxy_pass</span> <span class="s">http://fnando_dev</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">server</span> <span class="p">{</span>
  <span class="kn">listen</span>              <span class="mi">80</span><span class="p">;</span>
  <span class="kn">listen</span>              <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
  <span class="kn">server_name</span>         <span class="s">*.fnando.dev</span><span class="p">;</span>

  <span class="kn">ssl_certificate</span>     <span class="n">/Users/fnando/local/letsencrypt/live/fnando.dev/fullchain.pem</span><span class="p">;</span>
  <span class="kn">ssl_certificate_key</span> <span class="n">/Users/fnando/local/letsencrypt/live/fnando.dev/privkey.pem</span><span class="p">;</span>

  <span class="kn">ssl_protocols</span>       <span class="s">TLSv1</span> <span class="s">TLSv1.1</span> <span class="s">TLSv1.2</span><span class="p">;</span>
  <span class="kn">ssl_ciphers</span>         <span class="s">HIGH:!aNULL:!MD5</span><span class="p">;</span>

  <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
    <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$http_host</span><span class="p">;</span>
    <span class="kn">proxy_redirect</span> <span class="no">off</span><span class="p">;</span>
    <span class="kn">proxy_pass</span> <span class="s">http://fnando_dev</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Note que você não pode usar <code>~</code> para indicar o diretório home, então use o
caminho completo. Outra coisa que você precisa fazer é especificar as portas que
as aplicações web podem rodar no bloco <a href="https://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream">upstream</a>, então adicione a porta dos
frameworks que você usa ali.</p>

<p>Agora, reinicie o NGINX com o comando <code>sudo brew services restart nginx</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code>$ brew services restart nginx
Stopping `nginx`... (might take a while)
==&gt; Successfully stopped `nginx` (label: homebrew.mxcl.nginx)
==&gt; Successfully started `nginx` (label: homebrew.mxcl.nginx)
</code></pre></div>
<p>Se tudo deu certo, você já pode acessar sua aplicação web usando um domínio com
HTTPS, como <code>https://fnando.dev</code>. Para testar, inicie sua aplicação e visite
essa url.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/safari.png" alt="Exemplo de aplicação web rodando com HTTPS"></p>

<p>Como você pode ver, esse é um certificado 100% válido.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/safari-certificate.png" alt="Informações sobre o certificado no Safari"></p>

<p>E subdomínios funcionam sem problema algum, e sem precisar que você mexa no seu
arquivo <code>/etc/hosts</code>. 😎</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/letsencrypt/safari-subdomain.png" alt="Exemplo de aplicação web roando com subdomínios e HTTPS"></p>

<h2>Finalizando</h2>

<p>Você pode estar se perguntando por que desenvolver usando HTTPS, e a resposta é
simples: muitas coisas exigem HTTPS, como <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API">webauthn</a>. Você pode ver uma lista
completa de todas as funcionalidades que existem contexto seguro no <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts">site da
Mozilla</a>.</p>

<p>Eu já testei diversas combinações para fazer HTTPS funcionar em ambiente de
desenvolvimento, mas essa é de longe a melhor opção. Nenhum hack, nada de
adicionar certificados raíz na máquina. Obrigado, Let&rsquo;s Encrypt.</p>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/usando-lets-encrypt-em-desenvolvimento-com-nginx-e-aws-route53</link>
      <guid>https://nandovieira.com.br/usando-lets-encrypt-em-desenvolvimento-com-nginx-e-aws-route53</guid>
      <pubDate>Wed, 27 May 2020 12:44:00 -0700</pubDate>
    </item>
    <item>
      <title>Implementando dark mode em conteúdo web</title>
      <description>
        <![CDATA[<p>Bem-vindo a 2019, o ano que a tecnologia abraçou o <em>dark mode</em>. Tanto <a href="https://www.android.com/android-10/">Android 10</a> quanto <a href="https://www.apple.com/ios/ios-13/">iOS 13</a> tiveram uma nova versão no mês de setembro, e uma das funcionalidades mais aclamadas foi dark mode. Apps nativos podem implementar temas específicos para cada modo (i.e. dark e light), mas como fazer isso na web? Esse artigo mostra como você pode tirar vantagem dessa nova moda e deixar todo mundo feliz.</p>

<h2>Existem benefícios em se usar dark mode?</h2>

<p>Quando você começa a ler sobre dark mode, logo encontra frases como &ldquo;é melhor para os olhos&rdquo; ou &ldquo;mais eficiente no consumo de energia&rdquo;. A verdade é que para que sua bateria se beneficie de dark mode, é preciso que você tenha telas <a href="https://en.wikipedia.org/wiki/OLED">OLED</a>/<a href="https://en.wikipedia.org/wiki/AMOLED">AMOLED</a>, que só estão disponíveis em aparelhos novos mais caros.</p>

<ul>
<li><a href="https://support.apple.com/kb/sp770">iPhone X</a> ou mais novo.</li>
<li><a href="https://www.samsung.com/global/galaxy/galaxy-s10/specs/">Samsung Galaxy S10</a> ou mais novo.</li>
<li><a href="https://store.google.com/product/pixel_3_specs">Google Pixel 3</a> ou mais novo.</li>
<li><a href="https://consumer.huawei.com/en/phones/p30/specs/">Huawei P30</a><a href="https://consumer.huawei.com/en/phones/p30/specs/">huawei-p30</a> ou mais novo.</li>
</ul>

<p>Se você usa LCD ou outro tipo de tela, mudar as cores não irá estender o tempo de uso de sua bateria. Isso significa que a maior parte das pessoas não irá se beneficiar desse aspecto do dark mode.</p>

<p>Mas e quanto a benefícios à saúde? Com certeza é melhor, certo? Não é bem assim. Infelizmente não temos muitos estudos científicos sobre o assunto para dizermos com precisão que o dark mode é melhor. O que sabemos é que o tempo que passamos olhando para telas é o fator mais importante para o cansaço dos olhos. Quanto menos você ficar olhando sua tela melhor.</p>

<p>Alguns médicos também mencionam que mais contraste (seja tema claro, seja escuro) é mais importante que mudar o tema. E alguns médicos inclusive mencionam que dark mode pode ser mais prejudicial para pessoas com astigmatismo.</p>

<p>Eu não gosto muito de temas escuros com textos muito claros (i.e. fundo preto com texto branco); meus olhos ficam com aquela sensação de imagem fixa que demora um tempo até sumir. E o oposto também é verdade para algumas pessoas. O fato é que sem pesquisas sérias sobre o assunto tudo o que podemos fazer é falar sobre nossas experiências pessoais.</p>

<p>Agora, eu realmente acredito que a maior vantagem de dark mode é quando você está usando seu telefone em um quarto com pouca iluminação, mas você não deveria estar usando seu telefone antes de dormir de qualquer modo, certo?</p>

<h2>Suporte dos navegadores</h2>

<p>Qual o suporte dos navegadores para dark mode? Para detectar se um navegador tem suporte a dark mode ou não, você precisará da media query <a href="https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme"><code>(prefers-color-scheme: dark)</code></a>, disponível nos seguintes navegadores:</p>

<ul>
<li>macOS Safari 13 ou mais recente.</li>
<li>iOS Safari 13.2 ou mais recente.</li>
<li>Android Browser 76 ou mais recente.</li>
<li>Chrome for Android 78 ou mais recente.</li>
<li>Firefox for Android 68 ou mais recente.</li>
<li>Opera 62 ou mais recente.</li>
<li>Chrome 76 ou mais recente.</li>
<li>Firefox 67 ou mais recente.</li>
<li>Microsoft Edge 76 ou mais recente.</li>
</ul>

<p>Pois é… o versionamento de navegadores se tornou estúpido, e eu não deveria nem ter listado aqui de qualquer modo. Enfim, o que você precisa saber é que o Safari 13 é muito, muito novo, assim como o suporte para esse tipo de media query em todos os outros navegadores. Do ponto de vista de negócios, não faz muito sentido adicionar suporte para dark mode, mas você vai fazer isso de qualquer modo que eu sei. Então, aqui vai… é assim que você define seu CSS:</p>
<div class="highlight"><pre class="highlight css"><code><span class="nt">body</span> <span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="nl">background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nt">body</span> <span class="p">{</span>
    <span class="nl">background</span><span class="p">:</span> <span class="m">#2d3239</span><span class="p">;</span>
    <span class="nl">color</span><span class="p">:</span> <span class="m">#75715e</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nt">h1</span> <span class="p">{</span>
    <span class="nl">color</span><span class="p">:</span> <span class="m">#e9d970</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Você pode alternar entre um modo e outro no Web Inspector do Safari. Eu acho que o Chrome ainda não tem nada parecido, mas não deve demorar muito para lançarem algo similar. <a href="https://codepen.io/fnando/full/XWWzdbL">Veja este exemplo aqui.</a></p>

<video loop controls width="978" height="754" poster="https://nandovieira.s3.amazonaws.com/media/dark-mode/safari-dark-mode-inspector.jpg">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/safari-dark-mode-inspector.mp4" type="video/mp4">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/safari-dark-mode-inspector.webm" type="video/webm">
</video>

<p>Eu sei o que você pensando. O CSS pode sair de controle rapidinho, e concordo totalmente! A menos que você use variáveis. Mais especificamente, <a href="https://drafts.csswg.org/css-variables/">variáveis de CSS</a>.</p>

<h2>Criando temas com variáveis de CSS</h2>

<p>O truque para manter o CSS sobre controle é usar variáveis. Para fazer isso, você precisa usar o formato <code>--variable-name: value</code>.</p>
<div class="highlight"><pre class="highlight css"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--page-background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
<span class="p">}</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-background</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>É simples assim. Agora, como definir variáveis para os temas claro e escuro? Primeiro, você precisa declarar todas as variáveis que serão usadas sem nenhuma media query.</p>
<div class="highlight"><pre class="highlight css"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--page-background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
  <span class="py">--page-title</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="py">--page-text</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
<span class="p">}</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-background</span><span class="p">);</span>
  <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-text</span><span class="p">);</span>
<span class="p">}</span>

<span class="nt">h1</span> <span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-title</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>Depois, você pode adicionar as mesmas variávies usando a media query que casa dark mode. O CSS ficará parecido com isso:</p>
<div class="highlight"><pre class="highlight css"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--page-background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
  <span class="py">--page-title</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="py">--page-text</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nd">:root</span> <span class="p">{</span>
    <span class="py">--page-background</span><span class="p">:</span> <span class="m">#2d3239</span><span class="p">;</span>
    <span class="py">--page-title</span><span class="p">:</span> <span class="m">#e9d970</span><span class="p">;</span>
    <span class="py">--page-text</span><span class="p">:</span> <span class="m">#75715e</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-background</span><span class="p">);</span>
  <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-text</span><span class="p">);</span>
<span class="p">}</span>

<span class="nt">h1</span> <span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-title</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>Você pode <a href="https://codepen.io/fnando/pen/jOOarWO">ver este exemplo aqui</a>. Você pode definir qualquer propriedade do seu tema incluindo imagens de fundo, bordas, sombras e filtros.</p>

<h2>Dark mode em imagens e vídeos</h2>

<p>Não é incomum termos imagens/vídeos com bastante brilho por toda a página. Um truque que pode ajudar a reduzir a luminosidade de imagens e vídeos é usar filtros; você pode tentar uma combinação de <code>opacity</code> e <code>grayscale</code>. Adicione as variáveis <code>--image-grayscale</code> e <code>--image-opacity</code> e modifique até atingir algo que funcione para sua página.</p>
<div class="highlight"><pre class="highlight css"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--page-background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
  <span class="py">--page-title</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="py">--page-text</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="py">--image-grayscale</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="py">--image-opacity</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nd">:root</span> <span class="p">{</span>
    <span class="py">--page-background</span><span class="p">:</span> <span class="m">#2d3239</span><span class="p">;</span>
    <span class="py">--page-title</span><span class="p">:</span> <span class="m">#e9d970</span><span class="p">;</span>
    <span class="py">--page-text</span><span class="p">:</span> <span class="m">#75715e</span><span class="p">;</span>
    <span class="py">--image-grayscale</span><span class="p">:</span> <span class="m">50%</span><span class="p">;</span>
    <span class="py">--image-opacity</span><span class="p">:</span> <span class="m">60%</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nt">img</span><span class="o">,</span>
<span class="nt">video</span> <span class="p">{</span>
  <span class="nl">filter</span><span class="p">:</span> <span class="n">grayscale</span><span class="p">(</span><span class="n">var</span><span class="p">(</span><span class="n">--image-grayscale</span><span class="p">))</span> <span class="n">opacity</span><span class="p">(</span><span class="n">var</span><span class="p">(</span><span class="n">--image-opacity</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div>
<p><img src="https://nandovieira.s3.amazonaws.com/media/dark-mode/image-filters.jpg" alt="Uso de imagens diferentes em temas claros e escuros com uso de filtros de CSS"></p>

<p>Você <a href="https://codepen.io/fnando/full/WNNXxoN">pode ver este exemplo aqui</a>. Este truque pode não funcionar em todos os lugares e pode precisar de um elemento englobando as imagens e/ou vídeos. Eventualmente você precisará renderizar uma imagem completamente diferente. É aí que entra o elemento <a href="https://html.spec.whatwg.org/multipage/embedded-content.html#the-picture-element"><code>&lt;picture&gt;</code></a>.</p>

<p>O elemento <code>&lt;picture&gt;</code> tem suporte a media queries. Se você quer renderizar uma imagem diferente para dark mode, use um <code>&lt;source&gt;</code> diferente que tem <code>media=&quot;(prefers-color-scheme: dark)&quot;</code>. Se o navegador não tiver suporte para dark mode, ou se o usuário estiver usando um tema claro, então a imagem que será renderizada será a padrão.</p>
<div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;picture&gt;</span>
  <span class="nt">&lt;source</span> <span class="na">srcset=</span><span class="s">"beach.jpg"</span> <span class="na">media=</span><span class="s">"(prefers-color-scheme: dark)"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"pool.jpg"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/picture&gt;</span>
</code></pre></div>
<p>Agora, o CSS não precisa mais de nenhum código relacionado a filtros, mas deixo isso como exercício para você. O <a href="https://codepen.io/fnando/full/YzzEeLQ">resultado final pode ser visto aqui</a>.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/dark-mode/picture-element.jpg" alt="&lt;picture&gt; element in action!"></p>

<p>Em alguns casos, usar uma imagem SVG direto na página fará mais sentido. Basta mudar as cores com CSS e pronto! Essa técnica funciona muito bem para ícones, logos e coisas semelhantes. É importante mencionar que para poder estilizar elementos contidos em <code>&lt;svg&gt;</code> é preciso renderizar o SVG diretamente na página.  Usar a tag <code>&lt;img&gt;</code> não irá funcionar. Dito isso, nosso código para SVG pode ser algo assim:</p>
<div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;svg</span>
  <span class="na">id=</span><span class="s">"logo"</span>
  <span class="na">width=</span><span class="s">"250px"</span>
  <span class="na">height=</span><span class="s">"55px"</span>
  <span class="na">viewBox=</span><span class="s">"0 0 250 55"</span>
  <span class="na">version=</span><span class="s">"1.1"</span>
  <span class="na">xmlns=</span><span class="s">"http://www.w3.org/2000/svg"</span>
  <span class="na">xmlns:xlink=</span><span class="s">"http://www.w3.org/1999/xlink"</span>
<span class="nt">&gt;</span>
  <span class="nt">&lt;g</span> <span class="na">stroke=</span><span class="s">"none"</span> <span class="na">stroke-width=</span><span class="s">"1"</span> <span class="na">fill=</span><span class="s">"none"</span> <span class="na">fill-rule=</span><span class="s">"evenodd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;g</span> <span class="na">id=</span><span class="s">"logo"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;path</span> <span class="na">d=</span><span class="s">"..."</span> <span class="na">id=</span><span class="s">"background"</span> <span class="na">fill=</span><span class="s">"#0091FF"</span><span class="nt">&gt;&lt;/path&gt;</span>
      <span class="nt">&lt;path</span> <span class="na">d=</span><span class="s">"..."</span> <span class="na">id=</span><span class="s">"letter"</span> <span class="na">fill=</span><span class="s">"#FFD700"</span> <span class="na">fill-rule=</span><span class="s">"nonzero"</span><span class="nt">&gt;&lt;/path&gt;</span>
      <span class="nt">&lt;path</span> <span class="na">d=</span><span class="s">"..."</span> <span class="na">id=</span><span class="s">"words"</span> <span class="na">fill=</span><span class="s">"#45638B"</span> <span class="na">fill-rule=</span><span class="s">"nonzero"</span><span class="nt">&gt;&lt;/path&gt;</span>
    <span class="nt">&lt;/g&gt;</span>
  <span class="nt">&lt;/g&gt;</span>
<span class="nt">&lt;/svg&gt;</span>
</code></pre></div>
<p>As cores aplicadas no arquivo SVG são referentes ao tema padrão. Por isso, teremos que sobrescrever todas as cores para que funcionem em dark mode. Logo, esta tarefa será extremamente entediante se você tiver um SVG muito complexo.</p>
<div class="highlight"><pre class="highlight css"><code><span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nd">:root</span> <span class="p">{</span>
    <span class="py">--page-background</span><span class="p">:</span> <span class="m">#2d3239</span><span class="p">;</span>
    <span class="py">--page-title</span><span class="p">:</span> <span class="m">#e9d970</span><span class="p">;</span>
    <span class="py">--page-text</span><span class="p">:</span> <span class="m">#75715e</span><span class="p">;</span>

    <span class="py">--logo-background</span><span class="p">:</span> <span class="m">#4d5866</span><span class="p">;</span>
    <span class="py">--logo-words</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">#logo--words</span> <span class="p">{</span>
    <span class="py">fill</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--logo-words</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nf">#logo--background</span> <span class="p">{</span>
    <span class="py">fill</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--logo-background</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p><a href="https://codepen.io/fnando/full/abbVYJw">Veja este exemplo aqui</a>.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/dark-mode/svg-element.jpg" alt="Estilizando &lt;svg&gt; para dark mode"></p>

<p>Alternativamente, você pode usar <code>currentColor</code> como o valor das propriedades <code>fill</code> e <code>stroke</code>. Assim, só será preciso mudar a propriedade <code>color</code> do elemento SVG ou de um de seus elementos-pai. Essa abordagem funciona extremamente bem com <a href="https://codepen.io/fnando/full/RwwjYKb">ícones de linha</a>.</p>

<video loop controls width="1074" height="652" poster="https://nandovieira.s3.amazonaws.com/media/dark-mode/svg-current-color.jpg">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/svg-current-color.mp4" type="video/mp4">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/svg-current-color.webm" type="video/webm">
</video>

<p>O exemplo acima gera uma nova cor toda vez que o botão é clicado, definindo a propriedade <code>color</code> do elemento <code>&lt;body&gt;</code>. Isso é feito com <code>document.body.style.color = newColor</code>.</p>

<h2>Detectando dark mode com JavaScript</h2>

<p>Você pode precisar detectar dark mode com JavaScript para atualizar gráficos, por exemplo. Para isso será necessário usar <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"><code>window.matchMedia</code></a>. A detecção é bastante simples.</p>
<div class="highlight"><pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">isDarkMode</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">matchMedia</span> <span class="o">&amp;&amp;</span>
    <span class="nb">window</span><span class="p">.</span><span class="nf">matchMedia</span><span class="p">(</span><span class="dl">"</span><span class="s2">(prefers-color-scheme: dark)</span><span class="dl">"</span><span class="p">).</span><span class="nx">matches</span>
  <span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">renderCanvas</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nf">isDarkMode</span><span class="p">()</span>
    <span class="p">?</span> <span class="p">{</span> <span class="na">background</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#4d5866</span><span class="dl">"</span><span class="p">,</span> <span class="na">border</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#7091ba</span><span class="dl">"</span> <span class="p">}</span>
    <span class="p">:</span> <span class="p">{</span> <span class="na">background</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#ffffff</span><span class="dl">"</span><span class="p">,</span> <span class="na">border</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#000</span><span class="dl">"</span> <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">canvas</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nf">getContext</span><span class="p">(</span><span class="dl">"</span><span class="s2">2d</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">width</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">-</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">height</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">-</span> <span class="nx">y</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>

  <span class="nx">ctx</span><span class="p">.</span><span class="nf">clearRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">lineWidth</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">strokeStyle</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">border</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">strokeRect</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">background</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">fillRect</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span>
<span class="p">}</span>

<span class="nf">renderCanvas</span><span class="p">();</span>
</code></pre></div>
<p>Como você pode ver, basta definir o tema do código JavaScript condicionalmente quando você detectar o uso de dark mode. <a href="https://codepen.io/fnando/full/VwwrxPJ">Este exemplo pode ser visto aqui</a>.</p>

<p>Se você alternar entre um modo e outro (o seu computador pode estar configurado parar alternar entre os modos automaticamente), as cores do código renderizado pelo JavaScript ficariam erradas. Você pode usar <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener"><code>MediaQueryList.addListener</code></a> para detectar a mudança de temas e iniciar uma nova renderização do gráfico.</p>
<div class="highlight"><pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">darkModeMatcher</span> <span class="o">=</span>
  <span class="nb">window</span><span class="p">.</span><span class="nx">matchMedia</span> <span class="o">&amp;&amp;</span> <span class="nb">window</span><span class="p">.</span><span class="nf">matchMedia</span><span class="p">(</span><span class="dl">"</span><span class="s2">(prefers-color-scheme: dark)</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">function</span> <span class="nf">isDarkMode</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">darkModeMatcher</span> <span class="o">&amp;&amp;</span> <span class="nx">darkModeMatcher</span><span class="p">.</span><span class="nx">matches</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">onDarkModeChange</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">darkModeMatcher</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">darkModeMatcher</span><span class="p">.</span><span class="nf">addListener</span><span class="p">(({</span> <span class="nx">matches</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nf">callback</span><span class="p">(</span><span class="nx">matches</span><span class="p">));</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">renderCanvas</span><span class="p">(</span><span class="nx">useDarkTheme</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nx">useDarkTheme</span>
    <span class="p">?</span> <span class="p">{</span> <span class="na">background</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#4d5866</span><span class="dl">"</span><span class="p">,</span> <span class="na">border</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#7091ba</span><span class="dl">"</span> <span class="p">}</span>
    <span class="p">:</span> <span class="p">{</span> <span class="na">background</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#ffffff</span><span class="dl">"</span><span class="p">,</span> <span class="na">border</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#000</span><span class="dl">"</span> <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">canvas</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nf">getContext</span><span class="p">(</span><span class="dl">"</span><span class="s2">2d</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">width</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">-</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">height</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">-</span> <span class="nx">y</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>

  <span class="nx">ctx</span><span class="p">.</span><span class="nf">clearRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">lineWidth</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">strokeStyle</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">border</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">strokeRect</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">background</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">fillRect</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span>
<span class="p">}</span>

<span class="nf">renderCanvas</span><span class="p">(</span><span class="nf">isDarkMode</span><span class="p">());</span>
<span class="nf">onDarkModeChange</span><span class="p">(</span><span class="nx">renderCanvas</span><span class="p">);</span>
</code></pre></div>
<p>Esse código irá garantir que a função <code>renderCanvas</code> seja executada toda vez que o tema mudar. <a href="https://codepen.io/fnando/full/BaamVXG">Você pode ver este exemplo aqui</a>.</p>

<video loop controls width="978" height="754" poster="https://nandovieira.s3.amazonaws.com/media/dark-mode/match-media-listener.jpg">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/match-media-listener.mp4" type="video/mp4">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/match-media-listener.webm" type="video/webm">
</video>

<p>E com isso você tem praticamente todas as informações para implementar dark mode em conteúdos web.</p>

<h2>Finalizando</h2>

<p>Dark mode é a mais nova moda no mundo da tecnologia. E mesmo sem nenhum estudo sério comprovando os chamados &ldquo;benefícios do dark mode&rdquo;, você pode considerar implementar apenas pela felicidade de seus clientes. Os aspectos técnicos são bastante simples, mas não se engane: criar temas escuros é um processo desafiador, especialmente quando você precisa levar em consideração a direção de arte de todo o site, incluindo imagens e vídeos.</p>

<p>Outra coisa a se considerar é se você deve mudar o tema automaticamente ou se deve adicionar uma opção para que os usuários escolham o tema de preferência. Essa última opção também é simples de ser implementada se você adicionar uma classe ou atributo <em>data</em> em um elemento (e.g. <code>&lt;html data-theme=&quot;dark&quot;&gt;</code>), mas isto traz um problema que é ter que alternar imagens e vídeos manualmente quando existe a mudança de um tema para o outro. Ou isso, ou apenas ignore a mudança do tema, o que provalmente funcionará para a maioria dos casos.</p>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p>Bem-vindo a 2019, o ano que a tecnologia abraçou o <em>dark mode</em>. Tanto <a href="https://www.android.com/android-10/">Android 10</a> quanto <a href="https://www.apple.com/ios/ios-13/">iOS 13</a> tiveram uma nova versão no mês de setembro, e uma das funcionalidades mais aclamadas foi dark mode. Apps nativos podem implementar temas específicos para cada modo (i.e. dark e light), mas como fazer isso na web? Esse artigo mostra como você pode tirar vantagem dessa nova moda e deixar todo mundo feliz.</p>

<h2>Existem benefícios em se usar dark mode?</h2>

<p>Quando você começa a ler sobre dark mode, logo encontra frases como &ldquo;é melhor para os olhos&rdquo; ou &ldquo;mais eficiente no consumo de energia&rdquo;. A verdade é que para que sua bateria se beneficie de dark mode, é preciso que você tenha telas <a href="https://en.wikipedia.org/wiki/OLED">OLED</a>/<a href="https://en.wikipedia.org/wiki/AMOLED">AMOLED</a>, que só estão disponíveis em aparelhos novos mais caros.</p>

<ul>
<li><a href="https://support.apple.com/kb/sp770">iPhone X</a> ou mais novo.</li>
<li><a href="https://www.samsung.com/global/galaxy/galaxy-s10/specs/">Samsung Galaxy S10</a> ou mais novo.</li>
<li><a href="https://store.google.com/product/pixel_3_specs">Google Pixel 3</a> ou mais novo.</li>
<li><a href="https://consumer.huawei.com/en/phones/p30/specs/">Huawei P30</a><a href="https://consumer.huawei.com/en/phones/p30/specs/">huawei-p30</a> ou mais novo.</li>
</ul>

<p>Se você usa LCD ou outro tipo de tela, mudar as cores não irá estender o tempo de uso de sua bateria. Isso significa que a maior parte das pessoas não irá se beneficiar desse aspecto do dark mode.</p>

<p>Mas e quanto a benefícios à saúde? Com certeza é melhor, certo? Não é bem assim. Infelizmente não temos muitos estudos científicos sobre o assunto para dizermos com precisão que o dark mode é melhor. O que sabemos é que o tempo que passamos olhando para telas é o fator mais importante para o cansaço dos olhos. Quanto menos você ficar olhando sua tela melhor.</p>

<p>Alguns médicos também mencionam que mais contraste (seja tema claro, seja escuro) é mais importante que mudar o tema. E alguns médicos inclusive mencionam que dark mode pode ser mais prejudicial para pessoas com astigmatismo.</p>

<p>Eu não gosto muito de temas escuros com textos muito claros (i.e. fundo preto com texto branco); meus olhos ficam com aquela sensação de imagem fixa que demora um tempo até sumir. E o oposto também é verdade para algumas pessoas. O fato é que sem pesquisas sérias sobre o assunto tudo o que podemos fazer é falar sobre nossas experiências pessoais.</p>

<p>Agora, eu realmente acredito que a maior vantagem de dark mode é quando você está usando seu telefone em um quarto com pouca iluminação, mas você não deveria estar usando seu telefone antes de dormir de qualquer modo, certo?</p>

<h2>Suporte dos navegadores</h2>

<p>Qual o suporte dos navegadores para dark mode? Para detectar se um navegador tem suporte a dark mode ou não, você precisará da media query <a href="https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme"><code>(prefers-color-scheme: dark)</code></a>, disponível nos seguintes navegadores:</p>

<ul>
<li>macOS Safari 13 ou mais recente.</li>
<li>iOS Safari 13.2 ou mais recente.</li>
<li>Android Browser 76 ou mais recente.</li>
<li>Chrome for Android 78 ou mais recente.</li>
<li>Firefox for Android 68 ou mais recente.</li>
<li>Opera 62 ou mais recente.</li>
<li>Chrome 76 ou mais recente.</li>
<li>Firefox 67 ou mais recente.</li>
<li>Microsoft Edge 76 ou mais recente.</li>
</ul>

<p>Pois é… o versionamento de navegadores se tornou estúpido, e eu não deveria nem ter listado aqui de qualquer modo. Enfim, o que você precisa saber é que o Safari 13 é muito, muito novo, assim como o suporte para esse tipo de media query em todos os outros navegadores. Do ponto de vista de negócios, não faz muito sentido adicionar suporte para dark mode, mas você vai fazer isso de qualquer modo que eu sei. Então, aqui vai… é assim que você define seu CSS:</p>
<div class="highlight"><pre class="highlight css"><code><span class="nt">body</span> <span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="nl">background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nt">body</span> <span class="p">{</span>
    <span class="nl">background</span><span class="p">:</span> <span class="m">#2d3239</span><span class="p">;</span>
    <span class="nl">color</span><span class="p">:</span> <span class="m">#75715e</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nt">h1</span> <span class="p">{</span>
    <span class="nl">color</span><span class="p">:</span> <span class="m">#e9d970</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Você pode alternar entre um modo e outro no Web Inspector do Safari. Eu acho que o Chrome ainda não tem nada parecido, mas não deve demorar muito para lançarem algo similar. <a href="https://codepen.io/fnando/full/XWWzdbL">Veja este exemplo aqui.</a></p>

<video loop controls width="978" height="754" poster="https://nandovieira.s3.amazonaws.com/media/dark-mode/safari-dark-mode-inspector.jpg">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/safari-dark-mode-inspector.mp4" type="video/mp4">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/safari-dark-mode-inspector.webm" type="video/webm">
</video>

<p>Eu sei o que você pensando. O CSS pode sair de controle rapidinho, e concordo totalmente! A menos que você use variáveis. Mais especificamente, <a href="https://drafts.csswg.org/css-variables/">variáveis de CSS</a>.</p>

<h2>Criando temas com variáveis de CSS</h2>

<p>O truque para manter o CSS sobre controle é usar variáveis. Para fazer isso, você precisa usar o formato <code>--variable-name: value</code>.</p>
<div class="highlight"><pre class="highlight css"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--page-background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
<span class="p">}</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-background</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>É simples assim. Agora, como definir variáveis para os temas claro e escuro? Primeiro, você precisa declarar todas as variáveis que serão usadas sem nenhuma media query.</p>
<div class="highlight"><pre class="highlight css"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--page-background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
  <span class="py">--page-title</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="py">--page-text</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
<span class="p">}</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-background</span><span class="p">);</span>
  <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-text</span><span class="p">);</span>
<span class="p">}</span>

<span class="nt">h1</span> <span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-title</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>Depois, você pode adicionar as mesmas variávies usando a media query que casa dark mode. O CSS ficará parecido com isso:</p>
<div class="highlight"><pre class="highlight css"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--page-background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
  <span class="py">--page-title</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="py">--page-text</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nd">:root</span> <span class="p">{</span>
    <span class="py">--page-background</span><span class="p">:</span> <span class="m">#2d3239</span><span class="p">;</span>
    <span class="py">--page-title</span><span class="p">:</span> <span class="m">#e9d970</span><span class="p">;</span>
    <span class="py">--page-text</span><span class="p">:</span> <span class="m">#75715e</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-background</span><span class="p">);</span>
  <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-text</span><span class="p">);</span>
<span class="p">}</span>

<span class="nt">h1</span> <span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--page-title</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>Você pode <a href="https://codepen.io/fnando/pen/jOOarWO">ver este exemplo aqui</a>. Você pode definir qualquer propriedade do seu tema incluindo imagens de fundo, bordas, sombras e filtros.</p>

<h2>Dark mode em imagens e vídeos</h2>

<p>Não é incomum termos imagens/vídeos com bastante brilho por toda a página. Um truque que pode ajudar a reduzir a luminosidade de imagens e vídeos é usar filtros; você pode tentar uma combinação de <code>opacity</code> e <code>grayscale</code>. Adicione as variáveis <code>--image-grayscale</code> e <code>--image-opacity</code> e modifique até atingir algo que funcione para sua página.</p>
<div class="highlight"><pre class="highlight css"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--page-background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
  <span class="py">--page-title</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="py">--page-text</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="py">--image-grayscale</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="py">--image-opacity</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nd">:root</span> <span class="p">{</span>
    <span class="py">--page-background</span><span class="p">:</span> <span class="m">#2d3239</span><span class="p">;</span>
    <span class="py">--page-title</span><span class="p">:</span> <span class="m">#e9d970</span><span class="p">;</span>
    <span class="py">--page-text</span><span class="p">:</span> <span class="m">#75715e</span><span class="p">;</span>
    <span class="py">--image-grayscale</span><span class="p">:</span> <span class="m">50%</span><span class="p">;</span>
    <span class="py">--image-opacity</span><span class="p">:</span> <span class="m">60%</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nt">img</span><span class="o">,</span>
<span class="nt">video</span> <span class="p">{</span>
  <span class="nl">filter</span><span class="p">:</span> <span class="n">grayscale</span><span class="p">(</span><span class="n">var</span><span class="p">(</span><span class="n">--image-grayscale</span><span class="p">))</span> <span class="n">opacity</span><span class="p">(</span><span class="n">var</span><span class="p">(</span><span class="n">--image-opacity</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div>
<p><img src="https://nandovieira.s3.amazonaws.com/media/dark-mode/image-filters.jpg" alt="Uso de imagens diferentes em temas claros e escuros com uso de filtros de CSS"></p>

<p>Você <a href="https://codepen.io/fnando/full/WNNXxoN">pode ver este exemplo aqui</a>. Este truque pode não funcionar em todos os lugares e pode precisar de um elemento englobando as imagens e/ou vídeos. Eventualmente você precisará renderizar uma imagem completamente diferente. É aí que entra o elemento <a href="https://html.spec.whatwg.org/multipage/embedded-content.html#the-picture-element"><code>&lt;picture&gt;</code></a>.</p>

<p>O elemento <code>&lt;picture&gt;</code> tem suporte a media queries. Se você quer renderizar uma imagem diferente para dark mode, use um <code>&lt;source&gt;</code> diferente que tem <code>media=&quot;(prefers-color-scheme: dark)&quot;</code>. Se o navegador não tiver suporte para dark mode, ou se o usuário estiver usando um tema claro, então a imagem que será renderizada será a padrão.</p>
<div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;picture&gt;</span>
  <span class="nt">&lt;source</span> <span class="na">srcset=</span><span class="s">"beach.jpg"</span> <span class="na">media=</span><span class="s">"(prefers-color-scheme: dark)"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"pool.jpg"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/picture&gt;</span>
</code></pre></div>
<p>Agora, o CSS não precisa mais de nenhum código relacionado a filtros, mas deixo isso como exercício para você. O <a href="https://codepen.io/fnando/full/YzzEeLQ">resultado final pode ser visto aqui</a>.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/dark-mode/picture-element.jpg" alt="&lt;picture&gt; element in action!"></p>

<p>Em alguns casos, usar uma imagem SVG direto na página fará mais sentido. Basta mudar as cores com CSS e pronto! Essa técnica funciona muito bem para ícones, logos e coisas semelhantes. É importante mencionar que para poder estilizar elementos contidos em <code>&lt;svg&gt;</code> é preciso renderizar o SVG diretamente na página.  Usar a tag <code>&lt;img&gt;</code> não irá funcionar. Dito isso, nosso código para SVG pode ser algo assim:</p>
<div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;svg</span>
  <span class="na">id=</span><span class="s">"logo"</span>
  <span class="na">width=</span><span class="s">"250px"</span>
  <span class="na">height=</span><span class="s">"55px"</span>
  <span class="na">viewBox=</span><span class="s">"0 0 250 55"</span>
  <span class="na">version=</span><span class="s">"1.1"</span>
  <span class="na">xmlns=</span><span class="s">"http://www.w3.org/2000/svg"</span>
  <span class="na">xmlns:xlink=</span><span class="s">"http://www.w3.org/1999/xlink"</span>
<span class="nt">&gt;</span>
  <span class="nt">&lt;g</span> <span class="na">stroke=</span><span class="s">"none"</span> <span class="na">stroke-width=</span><span class="s">"1"</span> <span class="na">fill=</span><span class="s">"none"</span> <span class="na">fill-rule=</span><span class="s">"evenodd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;g</span> <span class="na">id=</span><span class="s">"logo"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;path</span> <span class="na">d=</span><span class="s">"..."</span> <span class="na">id=</span><span class="s">"background"</span> <span class="na">fill=</span><span class="s">"#0091FF"</span><span class="nt">&gt;&lt;/path&gt;</span>
      <span class="nt">&lt;path</span> <span class="na">d=</span><span class="s">"..."</span> <span class="na">id=</span><span class="s">"letter"</span> <span class="na">fill=</span><span class="s">"#FFD700"</span> <span class="na">fill-rule=</span><span class="s">"nonzero"</span><span class="nt">&gt;&lt;/path&gt;</span>
      <span class="nt">&lt;path</span> <span class="na">d=</span><span class="s">"..."</span> <span class="na">id=</span><span class="s">"words"</span> <span class="na">fill=</span><span class="s">"#45638B"</span> <span class="na">fill-rule=</span><span class="s">"nonzero"</span><span class="nt">&gt;&lt;/path&gt;</span>
    <span class="nt">&lt;/g&gt;</span>
  <span class="nt">&lt;/g&gt;</span>
<span class="nt">&lt;/svg&gt;</span>
</code></pre></div>
<p>As cores aplicadas no arquivo SVG são referentes ao tema padrão. Por isso, teremos que sobrescrever todas as cores para que funcionem em dark mode. Logo, esta tarefa será extremamente entediante se você tiver um SVG muito complexo.</p>
<div class="highlight"><pre class="highlight css"><code><span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nd">:root</span> <span class="p">{</span>
    <span class="py">--page-background</span><span class="p">:</span> <span class="m">#2d3239</span><span class="p">;</span>
    <span class="py">--page-title</span><span class="p">:</span> <span class="m">#e9d970</span><span class="p">;</span>
    <span class="py">--page-text</span><span class="p">:</span> <span class="m">#75715e</span><span class="p">;</span>

    <span class="py">--logo-background</span><span class="p">:</span> <span class="m">#4d5866</span><span class="p">;</span>
    <span class="py">--logo-words</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">#logo--words</span> <span class="p">{</span>
    <span class="py">fill</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--logo-words</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nf">#logo--background</span> <span class="p">{</span>
    <span class="py">fill</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--logo-background</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p><a href="https://codepen.io/fnando/full/abbVYJw">Veja este exemplo aqui</a>.</p>

<p><img src="https://nandovieira.s3.amazonaws.com/media/dark-mode/svg-element.jpg" alt="Estilizando &lt;svg&gt; para dark mode"></p>

<p>Alternativamente, você pode usar <code>currentColor</code> como o valor das propriedades <code>fill</code> e <code>stroke</code>. Assim, só será preciso mudar a propriedade <code>color</code> do elemento SVG ou de um de seus elementos-pai. Essa abordagem funciona extremamente bem com <a href="https://codepen.io/fnando/full/RwwjYKb">ícones de linha</a>.</p>

<video loop controls width="1074" height="652" poster="https://nandovieira.s3.amazonaws.com/media/dark-mode/svg-current-color.jpg">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/svg-current-color.mp4" type="video/mp4">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/svg-current-color.webm" type="video/webm">
</video>

<p>O exemplo acima gera uma nova cor toda vez que o botão é clicado, definindo a propriedade <code>color</code> do elemento <code>&lt;body&gt;</code>. Isso é feito com <code>document.body.style.color = newColor</code>.</p>

<h2>Detectando dark mode com JavaScript</h2>

<p>Você pode precisar detectar dark mode com JavaScript para atualizar gráficos, por exemplo. Para isso será necessário usar <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"><code>window.matchMedia</code></a>. A detecção é bastante simples.</p>
<div class="highlight"><pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">isDarkMode</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">matchMedia</span> <span class="o">&amp;&amp;</span>
    <span class="nb">window</span><span class="p">.</span><span class="nf">matchMedia</span><span class="p">(</span><span class="dl">"</span><span class="s2">(prefers-color-scheme: dark)</span><span class="dl">"</span><span class="p">).</span><span class="nx">matches</span>
  <span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">renderCanvas</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nf">isDarkMode</span><span class="p">()</span>
    <span class="p">?</span> <span class="p">{</span> <span class="na">background</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#4d5866</span><span class="dl">"</span><span class="p">,</span> <span class="na">border</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#7091ba</span><span class="dl">"</span> <span class="p">}</span>
    <span class="p">:</span> <span class="p">{</span> <span class="na">background</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#ffffff</span><span class="dl">"</span><span class="p">,</span> <span class="na">border</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#000</span><span class="dl">"</span> <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">canvas</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nf">getContext</span><span class="p">(</span><span class="dl">"</span><span class="s2">2d</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">width</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">-</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">height</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">-</span> <span class="nx">y</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>

  <span class="nx">ctx</span><span class="p">.</span><span class="nf">clearRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">lineWidth</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">strokeStyle</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">border</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">strokeRect</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">background</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">fillRect</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span>
<span class="p">}</span>

<span class="nf">renderCanvas</span><span class="p">();</span>
</code></pre></div>
<p>Como você pode ver, basta definir o tema do código JavaScript condicionalmente quando você detectar o uso de dark mode. <a href="https://codepen.io/fnando/full/VwwrxPJ">Este exemplo pode ser visto aqui</a>.</p>

<p>Se você alternar entre um modo e outro (o seu computador pode estar configurado parar alternar entre os modos automaticamente), as cores do código renderizado pelo JavaScript ficariam erradas. Você pode usar <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener"><code>MediaQueryList.addListener</code></a> para detectar a mudança de temas e iniciar uma nova renderização do gráfico.</p>
<div class="highlight"><pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">darkModeMatcher</span> <span class="o">=</span>
  <span class="nb">window</span><span class="p">.</span><span class="nx">matchMedia</span> <span class="o">&amp;&amp;</span> <span class="nb">window</span><span class="p">.</span><span class="nf">matchMedia</span><span class="p">(</span><span class="dl">"</span><span class="s2">(prefers-color-scheme: dark)</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">function</span> <span class="nf">isDarkMode</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">darkModeMatcher</span> <span class="o">&amp;&amp;</span> <span class="nx">darkModeMatcher</span><span class="p">.</span><span class="nx">matches</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">onDarkModeChange</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">darkModeMatcher</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">darkModeMatcher</span><span class="p">.</span><span class="nf">addListener</span><span class="p">(({</span> <span class="nx">matches</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nf">callback</span><span class="p">(</span><span class="nx">matches</span><span class="p">));</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">renderCanvas</span><span class="p">(</span><span class="nx">useDarkTheme</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nx">useDarkTheme</span>
    <span class="p">?</span> <span class="p">{</span> <span class="na">background</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#4d5866</span><span class="dl">"</span><span class="p">,</span> <span class="na">border</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#7091ba</span><span class="dl">"</span> <span class="p">}</span>
    <span class="p">:</span> <span class="p">{</span> <span class="na">background</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#ffffff</span><span class="dl">"</span><span class="p">,</span> <span class="na">border</span><span class="p">:</span> <span class="dl">"</span><span class="s2">#000</span><span class="dl">"</span> <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">canvas</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nf">getContext</span><span class="p">(</span><span class="dl">"</span><span class="s2">2d</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">width</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">-</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">height</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">-</span> <span class="nx">y</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>

  <span class="nx">ctx</span><span class="p">.</span><span class="nf">clearRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">lineWidth</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">strokeStyle</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">border</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">strokeRect</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">background</span><span class="p">;</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">fillRect</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span>
<span class="p">}</span>

<span class="nf">renderCanvas</span><span class="p">(</span><span class="nf">isDarkMode</span><span class="p">());</span>
<span class="nf">onDarkModeChange</span><span class="p">(</span><span class="nx">renderCanvas</span><span class="p">);</span>
</code></pre></div>
<p>Esse código irá garantir que a função <code>renderCanvas</code> seja executada toda vez que o tema mudar. <a href="https://codepen.io/fnando/full/BaamVXG">Você pode ver este exemplo aqui</a>.</p>

<video loop controls width="978" height="754" poster="https://nandovieira.s3.amazonaws.com/media/dark-mode/match-media-listener.jpg">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/match-media-listener.mp4" type="video/mp4">
  <source src="https://nandovieira.s3.amazonaws.com/media/dark-mode/match-media-listener.webm" type="video/webm">
</video>

<p>E com isso você tem praticamente todas as informações para implementar dark mode em conteúdos web.</p>

<h2>Finalizando</h2>

<p>Dark mode é a mais nova moda no mundo da tecnologia. E mesmo sem nenhum estudo sério comprovando os chamados &ldquo;benefícios do dark mode&rdquo;, você pode considerar implementar apenas pela felicidade de seus clientes. Os aspectos técnicos são bastante simples, mas não se engane: criar temas escuros é um processo desafiador, especialmente quando você precisa levar em consideração a direção de arte de todo o site, incluindo imagens e vídeos.</p>

<p>Outra coisa a se considerar é se você deve mudar o tema automaticamente ou se deve adicionar uma opção para que os usuários escolham o tema de preferência. Essa última opção também é simples de ser implementada se você adicionar uma classe ou atributo <em>data</em> em um elemento (e.g. <code>&lt;html data-theme=&quot;dark&quot;&gt;</code>), mas isto traz um problema que é ter que alternar imagens e vídeos manualmente quando existe a mudança de um tema para o outro. Ou isso, ou apenas ignore a mudança do tema, o que provalmente funcionará para a maioria dos casos.</p>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/implementando-dark-mode-em-conteudo-web</link>
      <guid>https://nandovieira.com.br/implementando-dark-mode-em-conteudo-web</guid>
      <pubDate>Thu, 31 Oct 2019 23:47:00 -0700</pubDate>
    </item>
    <item>
      <title>Configurando React Native no macOS Mojave</title>
      <description>
        <![CDATA[<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/react.svg" alt="React Native" class="align-right transparent" /></p>

<p>React Native é a plataforma de escolha para quem conhece <a href="https://reactjs.org">React</a> e precisa desenvolver aplicativos mobile. Em vez de usar uma abordagem híbrida como muitos projetos por aí, React Native tem como objetivo criar apps nativos com ferraments que já usamos para desenvolvimento web.</p>

<p>Em grande parte, essa abordagem funciona perfeitamente, especialmente se você faz parte de um time pequeno e não tem todos os recursos necessários para desenvolver código nativo para dispositivos Apple e Android. No entanto, é preciso estar ciente dos prós e contras, assim como qualquer decisão que você precisa tomar. Felizmente, o <a href="https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c">Airbnb</a> public um artigo muito legal que cobre esse assunto, então dê uma lida.</p>

<p>E depois de fazer sua pesquisa, você decidiu que React Native cobre suas necessidades. E agora? Neste artigo irei mostrar como configurar apps feitos com React Native para rodar em simuladores de iOS e Android, assim como aparelhos físicos.</p>

<h2>Instalando Xcode</h2>

<p>Você precisará de um Mac para usar o simulador de iOS. Você pode usar serviços como <a href="https://appetize.io">Appetize</a> (que eu não testei) para isso, mas já que o meu ambiente de desenvolvimento é o Mac, irei focar nele.</p>

<p>Muitas pessoas desenvolvem aplicações web e instalam apenas o command-line tools sem o Xcode para poder compilar extensões e usar o <a href="https://brew.sh">homebrew</a>. No entanto, você precisa do Xcode completo para rodar o simulador, então certifique-se de instalá-lo através da <a href="https://itunes.apple.com/us/app/xcode/id497799835?mt=12">App Store</a>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-xcode.png" alt="App Store: Xcode app page"></p>

<p>A instalação do Xcode vai levar um tempo. Quando terminar, abra o Xcode e instale os componentes extras.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-xcode-addons.png" alt="Xcode: Instalando components adicionais"></p>

<p>Agora você precisa instalar os simuladores de iOS. Vá em &ldquo;Preferences &gt; Components&rdquo; e baixe todos os simuladores que você precisar. Eu normalmente instalado apenas a versão mais recente, mas isso vai depender do suporte oficial que você quer dar aos apps.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-ios-simulators.png" alt="Xcode: Instalando os simuladores de iOS"></p>

<p>É isso! Agora vamos ver como instalar o simulador de Android.</p>

<h2>Instalando o Android Studio</h2>

<p>O ecossistema de Android é diferente do da Apple. Primeiro, é possível instalar diversos simuladores desenvolvidos por terceiros, com alternativas pagas inclusive. Se você não quer ter que escolher, sugiro que use o <a href="https://developer.android.com/studio">Android Studio</a>, a IDE oficial para desenvolvimento de apps para Android.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-studio-site.png" alt="Site do Android Studio"></p>

<p>Depois de fazer o download do Android Studio, move o app para o seu diretório <code>/Applications</code>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-studio-move-to-applications.png" alt="Android Studio: Movendo o app para o diretório /Applications"></p>

<p>Agora, abra o Android Studio e siga as instruções. Se você não instalou o Android SDK, você verá uma tela como esta:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-android-sdk-01.png" alt="Android Studio Setup Wizard: detecção de SDK"></p>

<p>Click em &ldquo;Next&rdquo; e o Android Studio irá instalar o SDK para você.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-android-sdk-02.png" alt="Android Studio Setup Wizard: Configuração de componentes"></p>

<p>Assim que a instalação terminar, você a tela de boas-vindas.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-studio-welcome-screen.png" alt="Android Studio: tela de boas-vindas"></p>

<p>Vá em &ldquo;Configure &gt; SDK Manager&rdquo;, e então &ldquo;SDK Tools&rdquo;. Clique no checkbox para selecionar o Build Tools, que serão usados pela command-line do React Native.  Clique em &ldquo;Apply&rdquo; para iniciar a instalação.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-sdk-manager.png" alt="Android Studio: SDK Manager"></p>

<p>Agora, note o diretório no qual foi instalado o Android SDK. Você vai precisar desse valor para definir uma variável de ambiente em seu terminal. Aqui as coisas podem complicar um pouco porque a sua configuração pode ser diferente da minha. De um modo geral, você terá um dos dois casos à seguir:</p>

<ul>
<li>Para bash, adicione as linhas abaixo ao arquivo <code>~/.bashrc</code>.</li>
<li>Para zsh, adicione as linhas abaixo ao arquivo <code>~/.zshrc</code>.</li>
</ul>
<div class="highlight"><pre class="highlight shell"><code><span class="nb">export </span><span class="nv">JAVA_HOME</span><span class="o">=</span><span class="s2">"/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"</span>
<span class="nb">export </span><span class="nv">ANDROID_HOME</span><span class="o">=</span><span class="nv">$HOME</span>/Library/Android/sdk
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$ANDROID_HOME</span><span class="s2">/platform-tools:</span><span class="nv">$ANDROID_HOME</span><span class="s2">/emulator:</span><span class="nv">$PATH</span><span class="s2">"</span>
</code></pre></div>
<p>Reinicie o seu terminal para recarregar suas configurações. Para verificar se tudo está funcionando corretamente, execute os comandos abaixo:</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="nv">$JAVA_HOME</span>
<span class="go">/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home

</span><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="nv">$ANDROID_HOME</span>
<span class="go">/Users/fnando/Library/Android/sdk

</span><span class="gp">$</span><span class="w"> </span>which adb
<span class="go">/Users/fnando/Library/Android/sdk/platform-tools/adb

</span><span class="gp">$</span><span class="w"> </span>which emulator
<span class="go">/Users/fnando/Library/Android/sdk/emulator/emulator
</span></code></pre></div>
<p>Se você receber uma saída muito diferente desta, verifique se você adicionou as linhas de <code>export</code> nos arquivos corretos e se reiniciou o terminal.</p>

<p>Agora você pode finalmente criar um novo dispositivo virtual. De volta à tela de boas-vindas, vá em &ldquo;Configure &gt; AVD Manager&rdquo;.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-virtual-device-manager.png" alt="Android Studio: Virtual Device Manager"></p>

<p>Clique em &ldquo;Create Virtual Device&rdquo; e escolha uma definição de dispositivo. Neste exemplo, estou escolhendo Pixel 3.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-avd-profile-chooser.png" alt="Android Studio: Virtual Device Profile Chooser"></p>

<p>Quando você terminar, clique em &ldquo;Next&rdquo;. Na tela seguinte você precisará escolher qual versão de Android irá usar. Você pode usar a última disponível, que neste momento é <a href="https://www.android.com/versions/pie-9-0/">Android Pie</a>. Certifique-se de clicar no link &ldquo;Download&rdquo;.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-pie.png" alt="Android Studio: System Image Selection"></p>

<p>Depois que o download do Android acabar, clique em &ldquo;Next&rdquo; mais uma vez. Você verá uma tela com o perfil do dispositivo que está criando. Clique em &ldquo;Finish&rdquo;.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-pie-virtual-device.png" alt="Android Studio: Pie System Image"></p>

<p>Você voltará à lista de dispositivos virtual disponíveis no seu computador. É aí que você iniciará o dispositivo, além de poder criar outros se precisar.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-avd-list.png" alt="Android Studio: Virtual Device List"></p>

<p>Para iniciar o simulador de Android, clique no botão de play disponível na coluna &ldquo;Actions&rdquo;. Lembre-se sempre de fazer isso. Caso contrário, você verá uma mensagem como <code>No connected devices!</code> quando for rodar React Native no simulador de Android.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-emulator-running.png" alt="Android Studio: Android Emulator Running"></p>

<p>Você também pode iniciar o emulator através da linha de comando. Para isso, basta usar o comando <code>emulator</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>emulator <span class="nt">-list-avds</span>
<span class="go">Pixel_3_API_28

</span><span class="gp">$</span><span class="w"> </span>emulator <span class="nt">-avd</span> Pixel_3_API_28
<span class="go">emulator: INFO: boot completed
emulator: INFO: boot time 34488 ms
emulator: Increasing screen off timeout, logcat buffer size to 2M.
</span></code></pre></div>
<p>Parabéns! Se tudo deu certo, você acabou de configurar o Android Studio e seu simulador. Agora, vamos iniciar um novo app para ver se tudo está funcionando.</p>

<h2>Configurando o React Native</h2>

<p>A documentação oficial recomenda instalar o react-native-cli globalmente, mas eu evito fazer isso. Eu prefiro usar o <code>npx</code> para gerar o esqueleto de nosso app.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>npx react-native-cli init sample
<span class="c">...
</span><span class="go">
✨  Done in 5.90s.

  Run instructions for iOS:
    • cd /Users/fnando/Projects/sample &amp;&amp; react-native run-ios
    - or -
    • Open ios/sample.xcodeproj in Xcode
    • Hit the Run button

  Run instructions for Android:
    • Have an Android emulator running (quickest way to get started), or a device connected.
    • cd /Users/fnando/Projects/sample &amp;&amp; react-native run-android
</span></code></pre></div>
<p>Entre no diretório do projeto e execute <code>react-native run-ios</code>. Isso irea compilar o app, instalá-lo no simulador e iniciar uma nova aba como o <a href="https://github.com/facebook/metro">Metro</a>, o bundler de JavaScript do React Native que foi criado pelo Facebook.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>react-native run-ios
<span class="c">...
</span><span class="go">info + exit 0

info

info ** BUILD SUCCEEDED **


info Installing build/sample/Build/Products/Debug-iphonesimulator/sample.app
info Launching org.reactjs.native.example.sample
org.reactjs.native.example.sample: 5043
</span></code></pre></div>
<p>Fique de olho na aba do Metro. É nela que você verá erros enquanto estiver desenvolvendo o app. Depois que toda a compilação inicial terminar, você deverá ver o app de exemplo rodando no simulador como na imagem abaixo.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/sample-ios-simulator.png" alt="Sample app running on iOS simulator"></p>

<p>Agora, e o simulador de Android? Certifique-se que o seu disposito virtual está rodando (lembre-se de fazer isso atráves da opção &ldquo;Configure &gt; AVD Manager&rdquo; da tela de boas-vindas). Você pode verificar todos os dispositivos em execução com o comando <code>adb devices</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>adb devices
<span class="go">List of devices attached
emulator-5554 device
</span></code></pre></div>
<p>Para executar o app no simulador de Android, execute o comando <code>react-native run-android</code>. Essa etapa irá compilar um monte de coisas e, então, irá instalar e iniciar o app de exemplo.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>react-native run-android
<span class="c">...
</span><span class="go">
BUILD SUCCESSFUL in 14s
26 actionable tasks: 26 executed
info Running /Users/fnando/Library/Android/sdk/platform-tools/adb -s emulator-5554 reverse tcp:8081 tcp:8081
info Starting the app on emulator-5554 (/Users/fnando/Library/Android/sdk/platform-tools/adb -s emulator-5554 shell am start -n com.sample/com.sample.MainActivity)...
Starting: Intent { cmp=com.sample/.MainActivity }
</span></code></pre></div>
<p>Se tudo deu certo, o simulador será iniciado como na imagem abaixo.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/sample-android-simulator.png" alt="Sample app running on Android simulator"></p>

<h2>Executando o app em dispositivos físicos</h2>

<p>Em algum momento, você deve testar o app em dispositivos físicos, ou seja, o aparelho de verdade. Isso ajudará a corrigir problemas na experiência que podem não incomodar tanto em simuladores.</p>

<h3>Executando o app no iPhone</h3>

<p>Para executar o app no iPhone, abra o arquivo <code>ios/sample.xcodeproj</code> no Xcode. Você pode fazer isso através do terminal com o comando <code>open ios/sample.xcodeproj</code>.</p>

<p>Primeiro, você terá que criar um novo perfil de desenvolvimento. Clique no nome do projeto, depois no target (nesse caso <code>sample</code>). Mude o bundle identifier para seu próprio domínio, ou não considerá compilar o projeto. Clique no botão &ldquo;Add Account&rdquo;, informe sua conta da Apple e selecione o seu nome no dropdown. Você também precisará selecionar o seu perfil de desenvolvimento para o target <code>sampleTests</code>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/xcode-developer-account.png" alt="Xcode developer account"></p>

<p>Conecte o seu iPhone ao computador e clique no seletor de simuladores.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/xcode-simulator-dropdown.png" alt="Xcode&#39;s simulator selector"></p>

<p>O seu iphone será listado no começo da lista.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/xcode-simulator-physical-device.png" alt="Physical iOS device selected"></p>

<p>Finalmente, clique no botão &ldquo;Build and Run&rdquo;, ou pressione <kbd>cmd-R</kbd>. Essa etapa irá instalar e abrir o app em seu iPhone.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/app-running-on-iphone-8.jpg" alt="Running app on physical iPhone 8"></p>

<h3>Executando o app no Android</h3>

<p>Eu não tinha um Android antes de começar a desenvolver com React Native. Por isso, decidi comprar um aparelho para testes e, depois de ver diversos reviews, decidi comprar um <a href="https://www.mi.com/global/mi-a2/">Xiaomi Mi A2</a>, que me custou por volta de $170 na Amazon. É um Android muito legal até mesmo para o uso diário, principalmente porque ele vem com o Android oficial, e não uma versão modificada que não presta.</p>

<p>Primeiro, você terá que ativar o Developer Options. Aqui a coisa pode complicar um pouco. No meu caso, eu tive que fazer isso em &ldquo;Settings &gt; About phone&rdquo; e dar tap 7 vezes no Build Number para ativar o modo de desenvolvimento. Depois disso, vá em &ldquo;Settings &gt; System &gt; Advanced &gt; Developer Options&rdquo; e ative &ldquo;USB Debugging&rdquo;.</p>

<p>Para verificar se o seu disposito pode ser visto pelo React Native, execute <code>adb devices</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>adb devices
<span class="go">List of devices attached
bbb5cc28  device
</span></code></pre></div>
<p>Finalmente, execute <code>react-native run-android</code>. Este comando irá instalar e abrir o app em seu Android.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/app-running-on-android-device.jpg" alt="Running app on physical Android device"></p>

<h2>Usando TypeScript</h2>

<p>Essa é uma etapa opcional, mas usar <a href="https://www.typescriptlang.org">TypeScript</a>, a linguagem tipada da Microsoft que compila para JavaScript, pode ser uma boa opção se você pretende publicar o seu app como um projeto open-source.</p>

<p>Para novos projetos, basta que você use o gerador de apps com <code>--template typescript</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>npx react-native-cli init sample <span class="nt">--template</span> typescript
<span class="c">...
</span><span class="go">✨  Done in 4.96s.

  Run instructions for iOS:
    • cd /Users/fnando/Projects/sample_ts &amp;&amp; react-native run-ios
    - or -
    • Open ios/sample_ts.xcodeproj in Xcode
    • Hit the Run button

  Run instructions for Android:
    • Have an Android emulator running (quickest way to get started), or a device connected.
    • cd /Users/fnando/Projects/sample_ts &amp;&amp; react-native run-android
</span></code></pre></div>
<p>Para projetos existentes, você terá que configurar manualmente tudo o que o template de TypeScript fornece, além de ter que renomear arquivos para as extensões <code>.ts</code> e <code>.tsx</code>. Eu não vou mostrar como fazer isso, mas existe um <a href="https://facebook.github.io/react-native/blog/2018/05/07/using-typescript-with-react-native">artigo publicado</a> no blog oficial que tem o passo-a-passo.</p>

<h2>Finalizando</h2>

<p>Apesar de todos os esforços em criar um bom ecossistema, o React Native ainda é um projeto imaturo e você verá que desenvolver apps para ele é bem desafiador. Mesmo com essas dificuldades, eu acredito que o React Native é uma excelente alternativa para times e empresas pequenos que precisam desenvolver apps nativos.</p>

<p>Antes de apostar em React Native, pergunte-se se um <a href="https://developers.google.com/web/progressive-web-apps/"><abbr title="Progressive Web App">PWA</abbr></a> pode ser uma alternativa viável. Infelizmente, <abbr title="Progressive Web App">PWA</abbr> vem com seus próprios desafios, como uma nova forma de instalar apps e inconsistências no suporte entre Android e iOS, assim como diversas limitações do dispositivo. Mesmo assim, pode ser um bom primeiro passo para se criar mobile apps quando responsive web não é suficiente, mas React Native é muito complicado.</p>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/react.svg" alt="React Native" class="align-right transparent" /></p>

<p>React Native é a plataforma de escolha para quem conhece <a href="https://reactjs.org">React</a> e precisa desenvolver aplicativos mobile. Em vez de usar uma abordagem híbrida como muitos projetos por aí, React Native tem como objetivo criar apps nativos com ferraments que já usamos para desenvolvimento web.</p>

<p>Em grande parte, essa abordagem funciona perfeitamente, especialmente se você faz parte de um time pequeno e não tem todos os recursos necessários para desenvolver código nativo para dispositivos Apple e Android. No entanto, é preciso estar ciente dos prós e contras, assim como qualquer decisão que você precisa tomar. Felizmente, o <a href="https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c">Airbnb</a> public um artigo muito legal que cobre esse assunto, então dê uma lida.</p>

<p>E depois de fazer sua pesquisa, você decidiu que React Native cobre suas necessidades. E agora? Neste artigo irei mostrar como configurar apps feitos com React Native para rodar em simuladores de iOS e Android, assim como aparelhos físicos.</p>

<h2>Instalando Xcode</h2>

<p>Você precisará de um Mac para usar o simulador de iOS. Você pode usar serviços como <a href="https://appetize.io">Appetize</a> (que eu não testei) para isso, mas já que o meu ambiente de desenvolvimento é o Mac, irei focar nele.</p>

<p>Muitas pessoas desenvolvem aplicações web e instalam apenas o command-line tools sem o Xcode para poder compilar extensões e usar o <a href="https://brew.sh">homebrew</a>. No entanto, você precisa do Xcode completo para rodar o simulador, então certifique-se de instalá-lo através da <a href="https://itunes.apple.com/us/app/xcode/id497799835?mt=12">App Store</a>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-xcode.png" alt="App Store: Xcode app page"></p>

<p>A instalação do Xcode vai levar um tempo. Quando terminar, abra o Xcode e instale os componentes extras.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-xcode-addons.png" alt="Xcode: Instalando components adicionais"></p>

<p>Agora você precisa instalar os simuladores de iOS. Vá em &ldquo;Preferences &gt; Components&rdquo; e baixe todos os simuladores que você precisar. Eu normalmente instalado apenas a versão mais recente, mas isso vai depender do suporte oficial que você quer dar aos apps.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-ios-simulators.png" alt="Xcode: Instalando os simuladores de iOS"></p>

<p>É isso! Agora vamos ver como instalar o simulador de Android.</p>

<h2>Instalando o Android Studio</h2>

<p>O ecossistema de Android é diferente do da Apple. Primeiro, é possível instalar diversos simuladores desenvolvidos por terceiros, com alternativas pagas inclusive. Se você não quer ter que escolher, sugiro que use o <a href="https://developer.android.com/studio">Android Studio</a>, a IDE oficial para desenvolvimento de apps para Android.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-studio-site.png" alt="Site do Android Studio"></p>

<p>Depois de fazer o download do Android Studio, move o app para o seu diretório <code>/Applications</code>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-studio-move-to-applications.png" alt="Android Studio: Movendo o app para o diretório /Applications"></p>

<p>Agora, abra o Android Studio e siga as instruções. Se você não instalou o Android SDK, você verá uma tela como esta:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-android-sdk-01.png" alt="Android Studio Setup Wizard: detecção de SDK"></p>

<p>Click em &ldquo;Next&rdquo; e o Android Studio irá instalar o SDK para você.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/install-android-sdk-02.png" alt="Android Studio Setup Wizard: Configuração de componentes"></p>

<p>Assim que a instalação terminar, você a tela de boas-vindas.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-studio-welcome-screen.png" alt="Android Studio: tela de boas-vindas"></p>

<p>Vá em &ldquo;Configure &gt; SDK Manager&rdquo;, e então &ldquo;SDK Tools&rdquo;. Clique no checkbox para selecionar o Build Tools, que serão usados pela command-line do React Native.  Clique em &ldquo;Apply&rdquo; para iniciar a instalação.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-sdk-manager.png" alt="Android Studio: SDK Manager"></p>

<p>Agora, note o diretório no qual foi instalado o Android SDK. Você vai precisar desse valor para definir uma variável de ambiente em seu terminal. Aqui as coisas podem complicar um pouco porque a sua configuração pode ser diferente da minha. De um modo geral, você terá um dos dois casos à seguir:</p>

<ul>
<li>Para bash, adicione as linhas abaixo ao arquivo <code>~/.bashrc</code>.</li>
<li>Para zsh, adicione as linhas abaixo ao arquivo <code>~/.zshrc</code>.</li>
</ul>
<div class="highlight"><pre class="highlight shell"><code><span class="nb">export </span><span class="nv">JAVA_HOME</span><span class="o">=</span><span class="s2">"/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"</span>
<span class="nb">export </span><span class="nv">ANDROID_HOME</span><span class="o">=</span><span class="nv">$HOME</span>/Library/Android/sdk
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$ANDROID_HOME</span><span class="s2">/platform-tools:</span><span class="nv">$ANDROID_HOME</span><span class="s2">/emulator:</span><span class="nv">$PATH</span><span class="s2">"</span>
</code></pre></div>
<p>Reinicie o seu terminal para recarregar suas configurações. Para verificar se tudo está funcionando corretamente, execute os comandos abaixo:</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="nv">$JAVA_HOME</span>
<span class="go">/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home

</span><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="nv">$ANDROID_HOME</span>
<span class="go">/Users/fnando/Library/Android/sdk

</span><span class="gp">$</span><span class="w"> </span>which adb
<span class="go">/Users/fnando/Library/Android/sdk/platform-tools/adb

</span><span class="gp">$</span><span class="w"> </span>which emulator
<span class="go">/Users/fnando/Library/Android/sdk/emulator/emulator
</span></code></pre></div>
<p>Se você receber uma saída muito diferente desta, verifique se você adicionou as linhas de <code>export</code> nos arquivos corretos e se reiniciou o terminal.</p>

<p>Agora você pode finalmente criar um novo dispositivo virtual. De volta à tela de boas-vindas, vá em &ldquo;Configure &gt; AVD Manager&rdquo;.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-virtual-device-manager.png" alt="Android Studio: Virtual Device Manager"></p>

<p>Clique em &ldquo;Create Virtual Device&rdquo; e escolha uma definição de dispositivo. Neste exemplo, estou escolhendo Pixel 3.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-avd-profile-chooser.png" alt="Android Studio: Virtual Device Profile Chooser"></p>

<p>Quando você terminar, clique em &ldquo;Next&rdquo;. Na tela seguinte você precisará escolher qual versão de Android irá usar. Você pode usar a última disponível, que neste momento é <a href="https://www.android.com/versions/pie-9-0/">Android Pie</a>. Certifique-se de clicar no link &ldquo;Download&rdquo;.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-pie.png" alt="Android Studio: System Image Selection"></p>

<p>Depois que o download do Android acabar, clique em &ldquo;Next&rdquo; mais uma vez. Você verá uma tela com o perfil do dispositivo que está criando. Clique em &ldquo;Finish&rdquo;.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-pie-virtual-device.png" alt="Android Studio: Pie System Image"></p>

<p>Você voltará à lista de dispositivos virtual disponíveis no seu computador. É aí que você iniciará o dispositivo, além de poder criar outros se precisar.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-avd-list.png" alt="Android Studio: Virtual Device List"></p>

<p>Para iniciar o simulador de Android, clique no botão de play disponível na coluna &ldquo;Actions&rdquo;. Lembre-se sempre de fazer isso. Caso contrário, você verá uma mensagem como <code>No connected devices!</code> quando for rodar React Native no simulador de Android.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/android-emulator-running.png" alt="Android Studio: Android Emulator Running"></p>

<p>Você também pode iniciar o emulator através da linha de comando. Para isso, basta usar o comando <code>emulator</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>emulator <span class="nt">-list-avds</span>
<span class="go">Pixel_3_API_28

</span><span class="gp">$</span><span class="w"> </span>emulator <span class="nt">-avd</span> Pixel_3_API_28
<span class="go">emulator: INFO: boot completed
emulator: INFO: boot time 34488 ms
emulator: Increasing screen off timeout, logcat buffer size to 2M.
</span></code></pre></div>
<p>Parabéns! Se tudo deu certo, você acabou de configurar o Android Studio e seu simulador. Agora, vamos iniciar um novo app para ver se tudo está funcionando.</p>

<h2>Configurando o React Native</h2>

<p>A documentação oficial recomenda instalar o react-native-cli globalmente, mas eu evito fazer isso. Eu prefiro usar o <code>npx</code> para gerar o esqueleto de nosso app.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>npx react-native-cli init sample
<span class="c">...
</span><span class="go">
✨  Done in 5.90s.

  Run instructions for iOS:
    • cd /Users/fnando/Projects/sample &amp;&amp; react-native run-ios
    - or -
    • Open ios/sample.xcodeproj in Xcode
    • Hit the Run button

  Run instructions for Android:
    • Have an Android emulator running (quickest way to get started), or a device connected.
    • cd /Users/fnando/Projects/sample &amp;&amp; react-native run-android
</span></code></pre></div>
<p>Entre no diretório do projeto e execute <code>react-native run-ios</code>. Isso irea compilar o app, instalá-lo no simulador e iniciar uma nova aba como o <a href="https://github.com/facebook/metro">Metro</a>, o bundler de JavaScript do React Native que foi criado pelo Facebook.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>react-native run-ios
<span class="c">...
</span><span class="go">info + exit 0

info

info ** BUILD SUCCEEDED **


info Installing build/sample/Build/Products/Debug-iphonesimulator/sample.app
info Launching org.reactjs.native.example.sample
org.reactjs.native.example.sample: 5043
</span></code></pre></div>
<p>Fique de olho na aba do Metro. É nela que você verá erros enquanto estiver desenvolvendo o app. Depois que toda a compilação inicial terminar, você deverá ver o app de exemplo rodando no simulador como na imagem abaixo.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/sample-ios-simulator.png" alt="Sample app running on iOS simulator"></p>

<p>Agora, e o simulador de Android? Certifique-se que o seu disposito virtual está rodando (lembre-se de fazer isso atráves da opção &ldquo;Configure &gt; AVD Manager&rdquo; da tela de boas-vindas). Você pode verificar todos os dispositivos em execução com o comando <code>adb devices</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>adb devices
<span class="go">List of devices attached
emulator-5554 device
</span></code></pre></div>
<p>Para executar o app no simulador de Android, execute o comando <code>react-native run-android</code>. Essa etapa irá compilar um monte de coisas e, então, irá instalar e iniciar o app de exemplo.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>react-native run-android
<span class="c">...
</span><span class="go">
BUILD SUCCESSFUL in 14s
26 actionable tasks: 26 executed
info Running /Users/fnando/Library/Android/sdk/platform-tools/adb -s emulator-5554 reverse tcp:8081 tcp:8081
info Starting the app on emulator-5554 (/Users/fnando/Library/Android/sdk/platform-tools/adb -s emulator-5554 shell am start -n com.sample/com.sample.MainActivity)...
Starting: Intent { cmp=com.sample/.MainActivity }
</span></code></pre></div>
<p>Se tudo deu certo, o simulador será iniciado como na imagem abaixo.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/sample-android-simulator.png" alt="Sample app running on Android simulator"></p>

<h2>Executando o app em dispositivos físicos</h2>

<p>Em algum momento, você deve testar o app em dispositivos físicos, ou seja, o aparelho de verdade. Isso ajudará a corrigir problemas na experiência que podem não incomodar tanto em simuladores.</p>

<h3>Executando o app no iPhone</h3>

<p>Para executar o app no iPhone, abra o arquivo <code>ios/sample.xcodeproj</code> no Xcode. Você pode fazer isso através do terminal com o comando <code>open ios/sample.xcodeproj</code>.</p>

<p>Primeiro, você terá que criar um novo perfil de desenvolvimento. Clique no nome do projeto, depois no target (nesse caso <code>sample</code>). Mude o bundle identifier para seu próprio domínio, ou não considerá compilar o projeto. Clique no botão &ldquo;Add Account&rdquo;, informe sua conta da Apple e selecione o seu nome no dropdown. Você também precisará selecionar o seu perfil de desenvolvimento para o target <code>sampleTests</code>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/xcode-developer-account.png" alt="Xcode developer account"></p>

<p>Conecte o seu iPhone ao computador e clique no seletor de simuladores.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/xcode-simulator-dropdown.png" alt="Xcode&#39;s simulator selector"></p>

<p>O seu iphone será listado no começo da lista.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/xcode-simulator-physical-device.png" alt="Physical iOS device selected"></p>

<p>Finalmente, clique no botão &ldquo;Build and Run&rdquo;, ou pressione <kbd>cmd-R</kbd>. Essa etapa irá instalar e abrir o app em seu iPhone.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/app-running-on-iphone-8.jpg" alt="Running app on physical iPhone 8"></p>

<h3>Executando o app no Android</h3>

<p>Eu não tinha um Android antes de começar a desenvolver com React Native. Por isso, decidi comprar um aparelho para testes e, depois de ver diversos reviews, decidi comprar um <a href="https://www.mi.com/global/mi-a2/">Xiaomi Mi A2</a>, que me custou por volta de $170 na Amazon. É um Android muito legal até mesmo para o uso diário, principalmente porque ele vem com o Android oficial, e não uma versão modificada que não presta.</p>

<p>Primeiro, você terá que ativar o Developer Options. Aqui a coisa pode complicar um pouco. No meu caso, eu tive que fazer isso em &ldquo;Settings &gt; About phone&rdquo; e dar tap 7 vezes no Build Number para ativar o modo de desenvolvimento. Depois disso, vá em &ldquo;Settings &gt; System &gt; Advanced &gt; Developer Options&rdquo; e ative &ldquo;USB Debugging&rdquo;.</p>

<p>Para verificar se o seu disposito pode ser visto pelo React Native, execute <code>adb devices</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>adb devices
<span class="go">List of devices attached
bbb5cc28  device
</span></code></pre></div>
<p>Finalmente, execute <code>react-native run-android</code>. Este comando irá instalar e abrir o app em seu Android.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/react-native-setup/app-running-on-android-device.jpg" alt="Running app on physical Android device"></p>

<h2>Usando TypeScript</h2>

<p>Essa é uma etapa opcional, mas usar <a href="https://www.typescriptlang.org">TypeScript</a>, a linguagem tipada da Microsoft que compila para JavaScript, pode ser uma boa opção se você pretende publicar o seu app como um projeto open-source.</p>

<p>Para novos projetos, basta que você use o gerador de apps com <code>--template typescript</code>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>npx react-native-cli init sample <span class="nt">--template</span> typescript
<span class="c">...
</span><span class="go">✨  Done in 4.96s.

  Run instructions for iOS:
    • cd /Users/fnando/Projects/sample_ts &amp;&amp; react-native run-ios
    - or -
    • Open ios/sample_ts.xcodeproj in Xcode
    • Hit the Run button

  Run instructions for Android:
    • Have an Android emulator running (quickest way to get started), or a device connected.
    • cd /Users/fnando/Projects/sample_ts &amp;&amp; react-native run-android
</span></code></pre></div>
<p>Para projetos existentes, você terá que configurar manualmente tudo o que o template de TypeScript fornece, além de ter que renomear arquivos para as extensões <code>.ts</code> e <code>.tsx</code>. Eu não vou mostrar como fazer isso, mas existe um <a href="https://facebook.github.io/react-native/blog/2018/05/07/using-typescript-with-react-native">artigo publicado</a> no blog oficial que tem o passo-a-passo.</p>

<h2>Finalizando</h2>

<p>Apesar de todos os esforços em criar um bom ecossistema, o React Native ainda é um projeto imaturo e você verá que desenvolver apps para ele é bem desafiador. Mesmo com essas dificuldades, eu acredito que o React Native é uma excelente alternativa para times e empresas pequenos que precisam desenvolver apps nativos.</p>

<p>Antes de apostar em React Native, pergunte-se se um <a href="https://developers.google.com/web/progressive-web-apps/"><abbr title="Progressive Web App">PWA</abbr></a> pode ser uma alternativa viável. Infelizmente, <abbr title="Progressive Web App">PWA</abbr> vem com seus próprios desafios, como uma nova forma de instalar apps e inconsistências no suporte entre Android e iOS, assim como diversas limitações do dispositivo. Mesmo assim, pode ser um bom primeiro passo para se criar mobile apps quando responsive web não é suficiente, mas React Native é muito complicado.</p>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/configurando-react-native-no-macos-mojave</link>
      <guid>https://nandovieira.com.br/configurando-react-native-no-macos-mojave</guid>
      <pubDate>Tue, 07 May 2019 14:31:00 -0700</pubDate>
    </item>
    <item>
      <title>O que mudou no Ruby 2.3</title>
      <description>
        <![CDATA[<p>Como já é tradição no mundo Ruby, provavelmente teremos uma nova versão em breve. Trata-se do Ruby 2.3, que está previsto para ser lançado em <a href="https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/ReleaseEngineering23">25 de dezembro de 2015</a>. </p>

<p>Neste artigo iremos ver quais são as mudanças mais interessantes que esta nova versão introduz. Para ver uma lista completa de todas as mudanças, <a href="https://github.com/ruby/ruby/blob/v2_3_0_preview2/NEWS">acesse a lista de mudanças</a>.</p>

<h2>Frozen strings</h2>

<p>Até o Ruby 2.2, as strings são mutáveis, ou seja, dada uma string, você pode alterar seu valor sem precisar instanciar um novo objeto.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">language</span> <span class="o">=</span> <span class="s2">"Ruby"</span>
<span class="c1">#=&gt; "Ruby"</span>

<span class="n">language</span><span class="p">.</span><span class="nf">upcase!</span>
<span class="c1">#=&gt; "RUBY"</span>

<span class="n">language</span>
<span class="c1">#=&gt; "RUBY"</span>
</code></pre></div>
<p>Para que isso aconteça, o Ruby tem que instanciar um novo objeto toda vez que você define uma string, mesmo que ela já tenha sido criada anteriormente.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">object_id</span>
<span class="c1">#=&gt; 70168324162560</span>

<span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">object_id</span>
<span class="c1">#=&gt; 70168315321340</span>
</code></pre></div>
<p>Perceba como os identificadores acima são diferentes. Isso acontece sempre que você definir uma nova string. Em alguns casos, você pode usar uma string imutável, que irá reutilizar este objeto. Quando você precisa de alguma otimização extra, definir uma constante e torná-la imutável irá ajudar:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Greeting</span>
  <span class="no">DEFAULT_GREETING</span> <span class="o">=</span> <span class="s2">"Hello there!"</span><span class="p">.</span><span class="nf">freeze</span>
<span class="k">end</span>
</code></pre></div>
<p>Em alguma situações, você pode ter um <a href="https://github.com/rails/rails/pull/21057">ganho de performance</a><sup id="fnref1"><a href="#fn1">1</a></sup> se reutilizar o mesmo objeto. O exemplo abaixo mostra a quantidade de objetos alocados em um loop.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">strings</span> <span class="o">=</span> <span class="p">[]</span>

<span class="n">before_count</span> <span class="o">=</span> <span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:total_allocated_objects</span><span class="p">]</span>

<span class="mi">10_000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="n">strings</span> <span class="o">&lt;&lt;</span> <span class="s2">"Ruby"</span> <span class="p">}</span>

<span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:total_allocated_objects</span><span class="p">]</span> <span class="o">-</span> <span class="n">before_count</span>
<span class="c1">#=&gt; 10001</span>
</code></pre></div>
<p>Como você pode ver, foram alocados 10001 objetos neste loop. Com uma simples mudança, o números de objetos alocados poderia cair para 1.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">strings</span> <span class="o">=</span> <span class="p">[]</span>

<span class="n">before_count</span> <span class="o">=</span> <span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:total_allocated_objects</span><span class="p">]</span>

<span class="mi">10_000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="n">strings</span> <span class="o">&lt;&lt;</span> <span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">freeze</span> <span class="p">}</span>

<span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:total_allocated_objects</span><span class="p">]</span> <span class="o">-</span> <span class="n">before_count</span>
<span class="c1">#=&gt; 1</span>
</code></pre></div>
<p>Muitas bibliotecas utilizam deste artifício para diminuir a quantidade de objetos alocados. Veja, por exemplo, como o <a href="http://rack.github.io">Rack</a> faz:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># rack-1.6.4/lib/rack.rb</span>
<span class="k">module</span> <span class="nn">Rack</span>
  <span class="c1"># ...</span>

  <span class="no">PATH_INFO</span>      <span class="o">=</span> <span class="s1">'PATH_INFO'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">REQUEST_METHOD</span> <span class="o">=</span> <span class="s1">'REQUEST_METHOD'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">SCRIPT_NAME</span>    <span class="o">=</span> <span class="s1">'SCRIPT_NAME'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">QUERY_STRING</span>   <span class="o">=</span> <span class="s1">'QUERY_STRING'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">CACHE_CONTROL</span>  <span class="o">=</span> <span class="s1">'Cache-Control'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">CONTENT_LENGTH</span> <span class="o">=</span> <span class="s1">'Content-Length'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">CONTENT_TYPE</span>   <span class="o">=</span> <span class="s1">'Content-Type'</span><span class="p">.</span><span class="nf">freeze</span>

  <span class="no">GET</span>  <span class="o">=</span> <span class="s1">'GET'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">HEAD</span> <span class="o">=</span> <span class="s1">'HEAD'</span><span class="p">.</span><span class="nf">freeze</span>

  <span class="c1"># ...</span>
<span class="k">end</span>
</code></pre></div>
<p>Posteriormente, essas constantes são utilizadas por todo o código, para evitar a alocação de strings que vão servir apenas para acessar hashes. Veja um benchmark simples mostrando a diferença entre strings mutáveis e imutáveis.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">require</span> <span class="s2">"benchmark/ips"</span>

<span class="no">GC</span><span class="p">.</span><span class="nf">disable</span>

<span class="no">CYCLES</span> <span class="o">=</span> <span class="mi">10_000_000</span>

<span class="no">Benchmark</span><span class="p">.</span><span class="nf">ips</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
  <span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"immutable"</span><span class="p">)</span> <span class="p">{</span><span class="no">CYCLES</span><span class="p">.</span><span class="nf">times</span><span class="p">{</span> <span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">freeze</span> <span class="p">}}</span>
  <span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"mutable"</span><span class="p">)</span> <span class="p">{</span><span class="no">CYCLES</span><span class="p">.</span><span class="nf">times</span><span class="p">{</span> <span class="s2">"Ruby"</span> <span class="p">}}</span>
  <span class="n">x</span><span class="p">.</span><span class="nf">compare!</span>
<span class="k">end</span>

<span class="c1"># Calculating -------------------------------------</span>
<span class="c1">#              mutable     1.000  i/100ms</span>
<span class="c1">#            immutable     1.000  i/100ms</span>
<span class="c1"># -------------------------------------------------</span>
<span class="c1">#              mutable      0.828  (±120.7%) i/s -      3.000  in   6.152329s</span>
<span class="c1">#            immutable      1.926  (± 0.0%) i/s  -     10.000 </span>
<span class="c1"># </span>
<span class="c1"># Comparison:</span>
<span class="c1">#            immutable:        1.9 i/s</span>
<span class="c1">#              mutable:        0.8 i/s - 2.33x slower</span>
</code></pre></div>
<p>No Ruby 2.3.0-preview2, strings mutáveis foram ~2x mais lentas que strings imutáveis. No ruby-2.2.3 essa diferença é ainda maior, chegando a ser 4x mais lentas.</p>

<p>Além disso, as alocações de objetos necessárias pelas strings mutáveis precisam sem removidas pelo Garbage Collector com mais frequência, como no exemplo do loop. Veja:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">GC</span><span class="p">.</span><span class="nf">start</span>
<span class="mi">1_000_000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="s2">"a"</span> <span class="p">}</span>
<span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:count</span><span class="p">]</span>
<span class="c1">#=&gt; 48</span>
</code></pre></div>
<p>Se formos usar apenas strings imutáveis, a quantidade de vezes que o Garbage Collector precisará executar é bem menor:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">GC</span><span class="p">.</span><span class="nf">start</span>
<span class="mi">1_000_000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="s2">"a"</span><span class="p">.</span><span class="nf">freeze</span> <span class="p">}</span>
<span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:count</span><span class="p">]</span>
<span class="c1">#=&gt; 5</span>
</code></pre></div>
<p>Pensando nisso, o Ruby trouxe suporte para strings imutáveis definidas no escopo de execução. Atualmente é possível ativar essa opção de modos diferentes; a maneira mais simples é usar o novo flag do interpretador, <code>--enable-frozen-string-literal</code><sup id="fnref2"><a href="#fn2">2</a></sup>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>ruby <span class="nt">--enable-frozen-string-literal</span> <span class="nt">-e</span> <span class="s1">'puts "Ruby".frozen?'</span>
<span class="go">true
</span></code></pre></div>
<p>Você também pode ativar esta opção com um <em>magic comment</em> por arquivo; isso significa que todas as strings definidas naquele arquivo serão imutáveis.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># frozen_string_literal: true</span>

<span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">frozen?</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>Para facilitar a conversão de strings, foram adicionados os métodos <code>String#+@</code> e <code>String#-@</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="p">(</span><span class="o">+</span><span class="s2">"Ruby"</span><span class="p">).</span><span class="nf">frozen?</span>
<span class="c1">#=&gt; false</span>

<span class="p">(</span><span class="o">-</span><span class="s2">"Ruby"</span><span class="p">).</span><span class="nf">frozen?</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>O método <code>String#+@</code> é particularmente útil para os casos onde você tem strings imutáveis por padrão e precisa modificar uma string, pois ele é <a href="https://gist.github.com/fnando/1460a8ae7c375eb16c4f">2x mais rápido</a> que <code>String#dup</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># frozen_string_literal: true</span>

<span class="nb">name</span> <span class="o">=</span> <span class="s2">"John"</span>
<span class="n">message</span> <span class="o">=</span> <span class="o">+</span><span class="s2">"Hello there, "</span> <span class="o">&lt;&lt;</span> <span class="nb">name</span> <span class="o">&lt;&lt;</span> <span class="s2">"!"</span>
<span class="c1">#=&gt; Hello there, John!</span>
</code></pre></div>
<p>Existe a possibilidade de <a href="https://bugs.ruby-lang.org/issues/11473">strings imutáveis serem o padrão</a> no Ruby 3.0, sem a necessidade de se configurar nada. Por isso, se você usa e abusa de mutabilidade de strings, está na hora de repensar sua estratégia.</p>

<p>Tickets: <a href="https://redmine.ruby-lang.org/issues/8976">#8976</a>, <a href="https://redmine.ruby-lang.org/issues/11725">#11725</a>, <a href="https://redmine.ruby-lang.org/issues/11782">#11782</a>.</p>

<h2>Safe navigation operator</h2>

<p>Um novo operador foi adicionado ao Ruby. Trata-se do <em>Safe navigation operator</em>, também conhecido como <em>lonely operator</em>. Ele funciona de forma semelhante ao método <code>Object#try</code>, utilizado principalmente em aplicações Ruby on Rails. Com ele você pode encadear chamadas sem a necessidade de verificar se o objeto tem um valor nulo.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">name</span> <span class="o">=</span> <span class="kp">nil</span>

<span class="c1"># ruby-2.3.0+</span>
<span class="nb">name</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">upcase</span>
<span class="c1">#=&gt; nil</span>

<span class="c1"># ActiveSupport</span>
<span class="nb">name</span><span class="p">.</span><span class="nf">try</span><span class="p">(</span><span class="ss">:upcase</span><span class="p">)</span>
<span class="c1">#=&gt; nil</span>
</code></pre></div>
<p>A maior diferença entre o lonely operator e a implementação adicionada pelo método <code>Object#try</code> é que a versão nativa fará <em>lazy evaluation</em>, executando expressões apenas quando o objeto é um valor diferente de <code>nil</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">def</span> <span class="nf">run</span>
  <span class="nb">puts</span> <span class="s2">"=&gt; Executed!"</span>
<span class="k">end</span>

<span class="kp">nil</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">run</span><span class="p">)</span>
<span class="c1">#=&gt; nil</span>
</code></pre></div>
<p>Com o ActiveSupport, o método <code>run</code> seria sempre executado.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">def</span> <span class="nf">run</span>
  <span class="nb">puts</span> <span class="s2">"=&gt; Executed!"</span>
<span class="k">end</span>

<span class="kp">nil</span><span class="p">.</span><span class="nf">try</span><span class="p">(</span><span class="n">run</span><span class="p">)</span>
<span class="c1">#=&gt; Executed!</span>
<span class="c1">#=&gt; nil</span>
</code></pre></div>
<p>Outra diferença importante é que a atribuições também são possíveis com o lonely operator.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Counter</span>
  <span class="nb">attr_accessor</span> <span class="ss">:count</span>

  <span class="k">def</span> <span class="nf">initialize</span>
    <span class="vi">@count</span> <span class="o">=</span> <span class="mi">0</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">counter</span> <span class="o">=</span> <span class="no">Counter</span><span class="p">.</span><span class="nf">new</span>
<span class="n">counter</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="c1">#=&gt; 1</span>
</code></pre></div>
<p>Finalmente, vale deixar claro que a implementação do lonely operator é mais parecida com a de <code>Object#try!</code>, que lançará uma exceção <code>NoMethodError</code> caso o método não exista no objeto-alvo quando este é um valor não-nulo.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">num</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1">#=&gt; 0</span>

<span class="n">num</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">upcase</span>
<span class="c1">#=&gt; NoMethodError: undefined method `upcase' for 0:Fixnum</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11537">#11537</a></p>

<h2>Acessando estruturas encadeadas</h2>

<p>Você provavelmente já precisou acessar objetos em uma estrutura encadeada, onde seu acesso parecia muito verboso. E uma preocupação muito comum é garantir que os objetos-pai existam, para evitar que exceções como <code>NoMethodError: undefined method [] for nil:NilClass</code> sejam lançadas. Com os métodos <code>Array#dig</code> e <code>Hash#dig</code> sua vida se tornou muito mais simples, sem a necessidade de usar uma biblioteca externa.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="p">{</span><span class="ss">b: </span><span class="p">{</span><span class="ss">c: </span><span class="s2">"hello"</span><span class="p">}}}</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">)</span>
<span class="c1">#=&gt; "hello"</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:e</span><span class="p">,</span> <span class="ss">:f</span><span class="p">)</span>
<span class="c1">#=&gt; nil</span>

<span class="n">array</span> <span class="o">=</span> <span class="p">[[[</span><span class="s2">"hello"</span><span class="p">]]]</span>

<span class="n">array</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="c1">#=&gt; "hello"</span>

<span class="n">array</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="c1">#=&gt; nil</span>
</code></pre></div>
<p>O mais interessante é que você pode, obviamente, usar isso para navegar uma estrutura mais complexa composta por arrays e hashes, como aquelas retornas por APIs. Veja a diferença para pegar o nome do primeiro usuário da estrutura à seguir:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">data</span> <span class="o">=</span> <span class="p">{</span><span class="ss">users: </span><span class="p">[{</span><span class="ss">name: </span><span class="s2">"John Doe"</span><span class="p">}]}</span>

<span class="c1"># ruby-2.2</span>
<span class="n">data</span><span class="p">[</span><span class="ss">:users</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="n">data</span><span class="p">[</span><span class="ss">:users</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="n">data</span><span class="p">[</span><span class="ss">:users</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="ss">:name</span><span class="p">]</span>
<span class="c1">#=&gt; "John Doe"</span>

<span class="c1"># ruby-2.3</span>
<span class="n">data</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="ss">:users</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">:name</span><span class="p">)</span>
<span class="c1">#=&gt; "John Doe"</span>
</code></pre></div>
<p>Vale lembrar que outras classes também implementam o método <code>dig</code>, como <code>Struct</code> e <code>OpenStruct</code>.</p>

<p>Tickets: <a href="https://redmine.ruby-lang.org/issues/11643">#11643</a> e <a href="https://redmine.ruby-lang.org/11688">#11688</a>.</p>

<h2>Hash#to_proc</h2>

<p>A classe <code>Hash</code> agora implementa o método <code>Hash#to_proc</code>, extremamente útil para os casos onde você precisa pegar todos os valores dada uma lista de chaves.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">2</span><span class="p">,</span> <span class="ss">c: </span><span class="mi">3</span><span class="p">}</span>
<span class="n">wanted_keys</span> <span class="o">=</span> <span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">]</span>

<span class="c1"># ruby-2.2</span>
<span class="n">wanted_keys</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">key</span><span class="o">|</span> <span class="nb">hash</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="p">}</span>
<span class="c1">#=&gt; [1, 3]</span>

<span class="c1"># ruby-2.3</span>
<span class="n">wanted_keys</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="nb">hash</span><span class="p">)</span>
<span class="c1">#=&gt; [1, 3]</span>
</code></pre></div>
<p>Para entender como isso funciona, vamos ver como seria uma possível implementação de <code>Hash#to_proc</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Hash</span>
  <span class="k">def</span> <span class="nf">to_proc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
    <span class="nb">proc</span> <span class="p">{</span><span class="o">|</span><span class="n">key</span><span class="o">|</span> <span class="nb">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="p">}</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="nb">hash</span><span class="p">.</span><span class="nf">to_proc</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="ss">:a</span><span class="p">)</span>
<span class="c1">#=&gt; 1</span>
</code></pre></div>
<ol>
<li>Toda vez que um objeto precisa ser convertido em <code>Proc</code>, o método <code>to_proc</code> é executado. Seria o equivalente a converter o objeto em bloco. Isso é feito implicitamente quando usamos <code>array.map(&amp;hash)</code>, por exemplo. </li>
<li>O método que recebe essa <code>Proc</code> irá, por sua vez, executá-la passando cada um dos itens do array como sendo o argumento da execução, como em <code>hash.to_proc.call(:a)</code>.</li>
<li>A <code>Proc</code> recebe a chave e retorna o valor associado a ela.</li>
</ol>

<p>A mesma ideia se aplica na implementação de <code>Symbol#to_proc</code>, ao qual já estamos acostumados. Uma possível implementação pode ser vista à seguir:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Symbol</span>
  <span class="k">def</span> <span class="nf">to_proc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
    <span class="nb">proc</span> <span class="p">{</span><span class="o">|</span><span class="n">target</span><span class="o">|</span> <span class="n">target</span><span class="p">.</span><span class="nf">public_send</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="p">}</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="ss">:upcase</span><span class="p">.</span><span class="nf">to_proc</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">)</span>
<span class="c1">#=&gt; "HELLO"</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11653">#11653</a>.</p>

<h2>Hash#fetch_values</h2>

<p>O método <code>Hash#fetch_values</code> retorna o valor de diversas chaves de uma só vez. Caso uma das chaves fornecidas não exista, a exceção <code>KeyError</code> será lançada. Essa é a principal diferença em relação ao já existente <code>Hash#values_at</code>, que retorna <code>nil</code> para chaves não encontradas.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">2</span><span class="p">,</span> <span class="ss">c: </span><span class="mi">3</span><span class="p">}</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">values_at</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">)</span>
<span class="c1">#=&gt; [1, 3]</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">fetch_values</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">)</span>
<span class="c1">#=&gt; [1, 3]</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">values_at</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">)</span>
<span class="c1">#=&gt; [1, nil]</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">fetch_values</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">)</span>
<span class="c1">#=&gt; KeyError: key not found: :d</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/10017">#10017</a>.</p>

<h2>Comparação de hashes</h2>

<p>Se você já precisou fazer comparações entre hashes, deve ter tentado usar o método <code>Hash#include?</code>. Imagine que você quer saber se o hash <code>a</code> contém o hash <code>b</code>. Parece natural fazer algo como <code>a.include?(b)</code>. Acontece que o método <code>Hash#include?</code> não passa de um alias para o método <code>Hash#has_key?</code> e irá apenas verificar se a chave é existente ou não.</p>

<p>Para resolver este problema, agora podemos usar alguns os métodos <code>Hash#&gt;</code>, <code>Hash#&gt;=</code>, <code>Hash#&lt;</code> e <code>Hash#&lt;=</code> para fazer este tipo de comparação.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># left é exatamente igual a right?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="o">==</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>

<span class="c1"># left contém e tem mais itens que right?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">1</span><span class="p">}</span> <span class="o">&gt;</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>

<span class="c1"># left contém right e, opcionalmente, possui mais itens?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="o">&gt;=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>

<span class="c1"># left contém right e possui menos itens?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="o">&lt;</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">2</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>

<span class="c1"># left contém right e, opcionalmente, possui menos itens?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="o">&lt;=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>Note que estes novos métodos não interferem na ordenação de hashes, já que o método <code>Hash#&lt;=&gt;</code> permaneceu inalterado.</p>

<p>A proposta original utilizava o método <code>Hash#contain?</code>, mas o Matz achou que este nome era ambíguo (embora ele não tenha especificado, acredito que é por causa do próprio <code>Hash#include?</code>).</p>

<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/10984">#10984</a>.</p>

<h2>Enumerable#grep_v</h2>

<p>O método <code>Enumerable#grep_v</code> é a contra-partida do método <code>Enumerable#grep</code> e emula o comportamento do comando <code>grep -v</code>, ignorando todos os itens que casarem a condição. No exemplo à seguir, estamos ignorando todas as cores que tiverem a letra <code>w</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">colors</span> <span class="o">=</span> <span class="sx">%w[white black brown]</span>

<span class="n">colors</span><span class="p">.</span><span class="nf">grep_v</span><span class="p">(</span><span class="sr">/w/</span><span class="p">)</span>
<span class="c1">#=&gt; ["black"]</span>
</code></pre></div>
<p>Tickets: <a href="https://redmine.ruby-lang.org/issues/11049">#11049</a> e <a href="https://redmine.ruby-lang.org/issues/11773">#11773</a>.</p>

<h2>Números positivos e negativos</h2>

<p>Sugerido pelo <a href="http://twitter.com/rafaelfranca">Rafael França</a> após serem adicionados ao <a href="https://github.com/rails/rails/commit/e54277a45da3c86fecdfa930663d7692fd083daa">ActiveSupport</a>, estes métodos permitem verificar se um número é positivo ou não, seguindo a mesma linha dos métodos <code>Numeric#zero?</code> e <code>Numeric#nonzero?</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="mi">1</span><span class="p">.</span><span class="nf">positive?</span>
<span class="c1">#=&gt; true</span>

<span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="nf">negative?</span>
<span class="c1">#=&gt; trues</span>

<span class="mi">0</span><span class="p">.</span><span class="nf">negative?</span>
<span class="c1">#=&gt; false</span>

<span class="mi">0</span><span class="p">.</span><span class="nf">positive?</span>
<span class="c1">#=&gt; false</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11151">#11151</a>.</p>

<h2>Module.deprecate_constant</h2>

<p>O método <code>Module.deprecate_constant</code> permite emitir um aviso quando uma constante é acessada.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">module</span> <span class="nn">A</span>
  <span class="k">module</span> <span class="nn">B</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="no">A</span><span class="p">.</span><span class="nf">deprecate_constant</span><span class="p">(</span><span class="ss">:B</span><span class="p">)</span>

<span class="no">A</span><span class="o">::</span><span class="no">B</span>
<span class="c1">#=&gt; warning: constant A::B is deprecated</span>
</code></pre></div>
<p>Um problema desta nova funcionalidade é que não é possível especificar qual é a alternativa para esta constante, caso haja uma. Idealmente deveria ser possível fazer algo como <code>A.deprecate_constant(:B, A::C)</code> para emitir o aviso <code>warning: constant A::B is deprecated; use A::C instead.</code>; se nenhuma alternativa fosse fornecida, exibiria a mensagem do exemplo anterior.</p>

<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11398">#11398</a>.</p>

<h2>Did You Mean</h2>

<p>O Ruby 2.3 agora possui integração nativa com a gem <a href="https://github.com/yuki24/did_you_mean">did_you_mean</a>, que melhora as mensagens de erros para casos de nomes incorretos de classes, métodos e variáveis (<em>typos</em>).</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># ruby 2.2</span>
<span class="no">Strin</span>
<span class="c1">#=&gt; NameError: uninitialized constant Strin</span>

<span class="c1"># ruby 2.3</span>
<span class="no">Strin</span>
<span class="c1">#=&gt; NameError: uninitialized constant Strin</span>
<span class="c1">#=&gt; Did you mean?  String</span>
<span class="c1">#=&gt;                STDIN</span>
</code></pre></div>
<p>O mesmo acontece com nomes de variáveis e métodos.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">users</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1">#=&gt; []</span>

<span class="n">user</span>
<span class="c1">#=&gt; NameError: undefined local variable or method `user' for main:Object</span>
<span class="c1">#=&gt; Did you mean?  users</span>

<span class="n">users</span><span class="p">.</span><span class="nf">revers</span>
<span class="c1">#=&gt; NoMethodError: undefined method `revers' for []:Array</span>
<span class="c1">#=&gt; Did you mean?  reverse</span>
<span class="c1">#=&gt;                reverse!</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11252">#11252</a>.</p>

<h2>Heredoc Strings</h2>

<p>O Ruby possui suporte para strings multilinhas. No entanto, em muitos casos é comum usarmos strings <em>heredoc</em>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">sql</span> <span class="o">=</span> <span class="o">&lt;&lt;</span><span class="no">SQL</span><span class="sh">
SELECT * 
FROM users
WHERE user_id = 1
</span><span class="no">SQL</span>
</code></pre></div>
<p>Perceba que no exemplo anterior, você precisa obrigatoriamente adicionar o delimitador <code>SQL</code> no começo da linha. Este delimitador pode ter o nome que você quiser e, em alguns editores como o Sublime Text, irá ativar o syntax highlighting.</p>

<p>Você também pode usar um outro tipo de heredoc que permite indentar o delimitador. Ele é especialmente útil quando você possui métodos.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">UserFinder</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">query</span>
    <span class="n">sql</span> <span class="o">=</span> <span class="o">&lt;&lt;-</span><span class="no">SQL</span><span class="sh">
      SELECT * 
      FROM users
      WHERE user_id = 1
</span><span class="no">    SQL</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>A maior desvantagem do <code>&lt;&lt;-</code> é que ele irá adicionar os espaçamentos da indentação na string. Isso significa que se você fizer o output daquela string, terá algo como</p>
<div class="highlight"><pre class="highlight plaintext"><code>      SELECT * 
      FROM users
      WHERE user_id = 1
</code></pre></div>
<p>em vez de</p>
<div class="highlight"><pre class="highlight plaintext"><code>SELECT * 
FROM users
WHERE user_id = 1
</code></pre></div>
<p>Se você tem acesso à biblioteca ActiveSupport, pode usar o método <code>String#strip_heredoc</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">UserFinder</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">query</span>
    <span class="n">sql</span> <span class="o">=</span> <span class="o">&lt;&lt;-</span><span class="no">SQL</span><span class="p">.</span><span class="nf">strip_heredoc</span><span class="sh">
      SELECT * 
      FROM users
      WHERE user_id = 1
</span><span class="no">    SQL</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>À partir do Ruby 2.3 você não precisa mais do método <code>String#strip_heredoc</code> e agora tem um novo tipo de heredoc. Trata-se de <code>&lt;&lt;~</code>, que ignora todos os espaçamentos da indentação, mantendo as quebras de linha.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">UserFinder</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">query</span>
    <span class="n">sql</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">SQL</span><span class="sh">
      SELECT * 
      FROM users
      WHERE user_id = 1
</span><span class="no">    SQL</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/9098">#9098</a>.</p>

<h2>Finalizando</h2>

<p>O Ruby vem melhorando incrementalmente ao longo dos anos. Depois da migração mais complicada de 1.8 para 1.9, não tivemos tantas mudanças drásticas que dificultaram a migração. E isso é uma característica importante para que a adoção de novas versões seja grande. Além disso, cada versão lançada tem tido melhorias de performance de 5 a 10% em comparação à versão anterior.</p>

<p>Para saber quais são os planos para o Ruby, recomendo que você assista o <a href="https://www.youtube.com/watch?v=LE0g2TUsJ4U">keynote do Matz</a> apresentado na Rubyconf deste ano. Dentre os objetivos está melhorar o suporte para multi-cores e programação concorrente, tornando a versão 3.0 três vezes mais rápida que a versão 2.0 em uma iniciativa chamada de Ruby3x3. O futuro do Ruby ainda é promissor.</p>

<div class="footnotes">
<hr>
<ol>

<li id="fn1">
<p><a href="http://www.schneems.com">Richard Schneeman</a> conseguiu 12% em um patch do Ruby on Rails onde a estratégia é, basicamente, utilizar strings imutáveis.&nbsp;<a href="#fnref1">&#8617;</a></p>
</li>

<li id="fn2">
<p>Lembre-se que as opções do interpretador Ruby também podem ser passadas através da variável de ambiente <code>RUBYOPT</code>. Sendo assim, podemos fazer algo como <code>RUBYOPT=&#39;--enable-frozen-string-literal&#39; ruby -e &#39;puts &quot;Ruby&quot;.frozen?&#39;</code>.&nbsp;<a href="#fnref2">&#8617;</a></p>
</li>

</ol>
</div>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p>Como já é tradição no mundo Ruby, provavelmente teremos uma nova versão em breve. Trata-se do Ruby 2.3, que está previsto para ser lançado em <a href="https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/ReleaseEngineering23">25 de dezembro de 2015</a>. </p>

<p>Neste artigo iremos ver quais são as mudanças mais interessantes que esta nova versão introduz. Para ver uma lista completa de todas as mudanças, <a href="https://github.com/ruby/ruby/blob/v2_3_0_preview2/NEWS">acesse a lista de mudanças</a>.</p>

<h2>Frozen strings</h2>

<p>Até o Ruby 2.2, as strings são mutáveis, ou seja, dada uma string, você pode alterar seu valor sem precisar instanciar um novo objeto.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">language</span> <span class="o">=</span> <span class="s2">"Ruby"</span>
<span class="c1">#=&gt; "Ruby"</span>

<span class="n">language</span><span class="p">.</span><span class="nf">upcase!</span>
<span class="c1">#=&gt; "RUBY"</span>

<span class="n">language</span>
<span class="c1">#=&gt; "RUBY"</span>
</code></pre></div>
<p>Para que isso aconteça, o Ruby tem que instanciar um novo objeto toda vez que você define uma string, mesmo que ela já tenha sido criada anteriormente.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">object_id</span>
<span class="c1">#=&gt; 70168324162560</span>

<span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">object_id</span>
<span class="c1">#=&gt; 70168315321340</span>
</code></pre></div>
<p>Perceba como os identificadores acima são diferentes. Isso acontece sempre que você definir uma nova string. Em alguns casos, você pode usar uma string imutável, que irá reutilizar este objeto. Quando você precisa de alguma otimização extra, definir uma constante e torná-la imutável irá ajudar:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Greeting</span>
  <span class="no">DEFAULT_GREETING</span> <span class="o">=</span> <span class="s2">"Hello there!"</span><span class="p">.</span><span class="nf">freeze</span>
<span class="k">end</span>
</code></pre></div>
<p>Em alguma situações, você pode ter um <a href="https://github.com/rails/rails/pull/21057">ganho de performance</a><sup id="fnref1"><a href="#fn1">1</a></sup> se reutilizar o mesmo objeto. O exemplo abaixo mostra a quantidade de objetos alocados em um loop.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">strings</span> <span class="o">=</span> <span class="p">[]</span>

<span class="n">before_count</span> <span class="o">=</span> <span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:total_allocated_objects</span><span class="p">]</span>

<span class="mi">10_000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="n">strings</span> <span class="o">&lt;&lt;</span> <span class="s2">"Ruby"</span> <span class="p">}</span>

<span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:total_allocated_objects</span><span class="p">]</span> <span class="o">-</span> <span class="n">before_count</span>
<span class="c1">#=&gt; 10001</span>
</code></pre></div>
<p>Como você pode ver, foram alocados 10001 objetos neste loop. Com uma simples mudança, o números de objetos alocados poderia cair para 1.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">strings</span> <span class="o">=</span> <span class="p">[]</span>

<span class="n">before_count</span> <span class="o">=</span> <span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:total_allocated_objects</span><span class="p">]</span>

<span class="mi">10_000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="n">strings</span> <span class="o">&lt;&lt;</span> <span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">freeze</span> <span class="p">}</span>

<span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:total_allocated_objects</span><span class="p">]</span> <span class="o">-</span> <span class="n">before_count</span>
<span class="c1">#=&gt; 1</span>
</code></pre></div>
<p>Muitas bibliotecas utilizam deste artifício para diminuir a quantidade de objetos alocados. Veja, por exemplo, como o <a href="http://rack.github.io">Rack</a> faz:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># rack-1.6.4/lib/rack.rb</span>
<span class="k">module</span> <span class="nn">Rack</span>
  <span class="c1"># ...</span>

  <span class="no">PATH_INFO</span>      <span class="o">=</span> <span class="s1">'PATH_INFO'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">REQUEST_METHOD</span> <span class="o">=</span> <span class="s1">'REQUEST_METHOD'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">SCRIPT_NAME</span>    <span class="o">=</span> <span class="s1">'SCRIPT_NAME'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">QUERY_STRING</span>   <span class="o">=</span> <span class="s1">'QUERY_STRING'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">CACHE_CONTROL</span>  <span class="o">=</span> <span class="s1">'Cache-Control'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">CONTENT_LENGTH</span> <span class="o">=</span> <span class="s1">'Content-Length'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">CONTENT_TYPE</span>   <span class="o">=</span> <span class="s1">'Content-Type'</span><span class="p">.</span><span class="nf">freeze</span>

  <span class="no">GET</span>  <span class="o">=</span> <span class="s1">'GET'</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="no">HEAD</span> <span class="o">=</span> <span class="s1">'HEAD'</span><span class="p">.</span><span class="nf">freeze</span>

  <span class="c1"># ...</span>
<span class="k">end</span>
</code></pre></div>
<p>Posteriormente, essas constantes são utilizadas por todo o código, para evitar a alocação de strings que vão servir apenas para acessar hashes. Veja um benchmark simples mostrando a diferença entre strings mutáveis e imutáveis.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">require</span> <span class="s2">"benchmark/ips"</span>

<span class="no">GC</span><span class="p">.</span><span class="nf">disable</span>

<span class="no">CYCLES</span> <span class="o">=</span> <span class="mi">10_000_000</span>

<span class="no">Benchmark</span><span class="p">.</span><span class="nf">ips</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
  <span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"immutable"</span><span class="p">)</span> <span class="p">{</span><span class="no">CYCLES</span><span class="p">.</span><span class="nf">times</span><span class="p">{</span> <span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">freeze</span> <span class="p">}}</span>
  <span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"mutable"</span><span class="p">)</span> <span class="p">{</span><span class="no">CYCLES</span><span class="p">.</span><span class="nf">times</span><span class="p">{</span> <span class="s2">"Ruby"</span> <span class="p">}}</span>
  <span class="n">x</span><span class="p">.</span><span class="nf">compare!</span>
<span class="k">end</span>

<span class="c1"># Calculating -------------------------------------</span>
<span class="c1">#              mutable     1.000  i/100ms</span>
<span class="c1">#            immutable     1.000  i/100ms</span>
<span class="c1"># -------------------------------------------------</span>
<span class="c1">#              mutable      0.828  (±120.7%) i/s -      3.000  in   6.152329s</span>
<span class="c1">#            immutable      1.926  (± 0.0%) i/s  -     10.000 </span>
<span class="c1"># </span>
<span class="c1"># Comparison:</span>
<span class="c1">#            immutable:        1.9 i/s</span>
<span class="c1">#              mutable:        0.8 i/s - 2.33x slower</span>
</code></pre></div>
<p>No Ruby 2.3.0-preview2, strings mutáveis foram ~2x mais lentas que strings imutáveis. No ruby-2.2.3 essa diferença é ainda maior, chegando a ser 4x mais lentas.</p>

<p>Além disso, as alocações de objetos necessárias pelas strings mutáveis precisam sem removidas pelo Garbage Collector com mais frequência, como no exemplo do loop. Veja:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">GC</span><span class="p">.</span><span class="nf">start</span>
<span class="mi">1_000_000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="s2">"a"</span> <span class="p">}</span>
<span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:count</span><span class="p">]</span>
<span class="c1">#=&gt; 48</span>
</code></pre></div>
<p>Se formos usar apenas strings imutáveis, a quantidade de vezes que o Garbage Collector precisará executar é bem menor:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">GC</span><span class="p">.</span><span class="nf">start</span>
<span class="mi">1_000_000</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="s2">"a"</span><span class="p">.</span><span class="nf">freeze</span> <span class="p">}</span>
<span class="no">GC</span><span class="p">.</span><span class="nf">stat</span><span class="p">[</span><span class="ss">:count</span><span class="p">]</span>
<span class="c1">#=&gt; 5</span>
</code></pre></div>
<p>Pensando nisso, o Ruby trouxe suporte para strings imutáveis definidas no escopo de execução. Atualmente é possível ativar essa opção de modos diferentes; a maneira mais simples é usar o novo flag do interpretador, <code>--enable-frozen-string-literal</code><sup id="fnref2"><a href="#fn2">2</a></sup>.</p>
<div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>ruby <span class="nt">--enable-frozen-string-literal</span> <span class="nt">-e</span> <span class="s1">'puts "Ruby".frozen?'</span>
<span class="go">true
</span></code></pre></div>
<p>Você também pode ativar esta opção com um <em>magic comment</em> por arquivo; isso significa que todas as strings definidas naquele arquivo serão imutáveis.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># frozen_string_literal: true</span>

<span class="s2">"Ruby"</span><span class="p">.</span><span class="nf">frozen?</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>Para facilitar a conversão de strings, foram adicionados os métodos <code>String#+@</code> e <code>String#-@</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="p">(</span><span class="o">+</span><span class="s2">"Ruby"</span><span class="p">).</span><span class="nf">frozen?</span>
<span class="c1">#=&gt; false</span>

<span class="p">(</span><span class="o">-</span><span class="s2">"Ruby"</span><span class="p">).</span><span class="nf">frozen?</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>O método <code>String#+@</code> é particularmente útil para os casos onde você tem strings imutáveis por padrão e precisa modificar uma string, pois ele é <a href="https://gist.github.com/fnando/1460a8ae7c375eb16c4f">2x mais rápido</a> que <code>String#dup</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># frozen_string_literal: true</span>

<span class="nb">name</span> <span class="o">=</span> <span class="s2">"John"</span>
<span class="n">message</span> <span class="o">=</span> <span class="o">+</span><span class="s2">"Hello there, "</span> <span class="o">&lt;&lt;</span> <span class="nb">name</span> <span class="o">&lt;&lt;</span> <span class="s2">"!"</span>
<span class="c1">#=&gt; Hello there, John!</span>
</code></pre></div>
<p>Existe a possibilidade de <a href="https://bugs.ruby-lang.org/issues/11473">strings imutáveis serem o padrão</a> no Ruby 3.0, sem a necessidade de se configurar nada. Por isso, se você usa e abusa de mutabilidade de strings, está na hora de repensar sua estratégia.</p>

<p>Tickets: <a href="https://redmine.ruby-lang.org/issues/8976">#8976</a>, <a href="https://redmine.ruby-lang.org/issues/11725">#11725</a>, <a href="https://redmine.ruby-lang.org/issues/11782">#11782</a>.</p>

<h2>Safe navigation operator</h2>

<p>Um novo operador foi adicionado ao Ruby. Trata-se do <em>Safe navigation operator</em>, também conhecido como <em>lonely operator</em>. Ele funciona de forma semelhante ao método <code>Object#try</code>, utilizado principalmente em aplicações Ruby on Rails. Com ele você pode encadear chamadas sem a necessidade de verificar se o objeto tem um valor nulo.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">name</span> <span class="o">=</span> <span class="kp">nil</span>

<span class="c1"># ruby-2.3.0+</span>
<span class="nb">name</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">upcase</span>
<span class="c1">#=&gt; nil</span>

<span class="c1"># ActiveSupport</span>
<span class="nb">name</span><span class="p">.</span><span class="nf">try</span><span class="p">(</span><span class="ss">:upcase</span><span class="p">)</span>
<span class="c1">#=&gt; nil</span>
</code></pre></div>
<p>A maior diferença entre o lonely operator e a implementação adicionada pelo método <code>Object#try</code> é que a versão nativa fará <em>lazy evaluation</em>, executando expressões apenas quando o objeto é um valor diferente de <code>nil</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">def</span> <span class="nf">run</span>
  <span class="nb">puts</span> <span class="s2">"=&gt; Executed!"</span>
<span class="k">end</span>

<span class="kp">nil</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">run</span><span class="p">)</span>
<span class="c1">#=&gt; nil</span>
</code></pre></div>
<p>Com o ActiveSupport, o método <code>run</code> seria sempre executado.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">def</span> <span class="nf">run</span>
  <span class="nb">puts</span> <span class="s2">"=&gt; Executed!"</span>
<span class="k">end</span>

<span class="kp">nil</span><span class="p">.</span><span class="nf">try</span><span class="p">(</span><span class="n">run</span><span class="p">)</span>
<span class="c1">#=&gt; Executed!</span>
<span class="c1">#=&gt; nil</span>
</code></pre></div>
<p>Outra diferença importante é que a atribuições também são possíveis com o lonely operator.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Counter</span>
  <span class="nb">attr_accessor</span> <span class="ss">:count</span>

  <span class="k">def</span> <span class="nf">initialize</span>
    <span class="vi">@count</span> <span class="o">=</span> <span class="mi">0</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">counter</span> <span class="o">=</span> <span class="no">Counter</span><span class="p">.</span><span class="nf">new</span>
<span class="n">counter</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="c1">#=&gt; 1</span>
</code></pre></div>
<p>Finalmente, vale deixar claro que a implementação do lonely operator é mais parecida com a de <code>Object#try!</code>, que lançará uma exceção <code>NoMethodError</code> caso o método não exista no objeto-alvo quando este é um valor não-nulo.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">num</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1">#=&gt; 0</span>

<span class="n">num</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">upcase</span>
<span class="c1">#=&gt; NoMethodError: undefined method `upcase' for 0:Fixnum</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11537">#11537</a></p>

<h2>Acessando estruturas encadeadas</h2>

<p>Você provavelmente já precisou acessar objetos em uma estrutura encadeada, onde seu acesso parecia muito verboso. E uma preocupação muito comum é garantir que os objetos-pai existam, para evitar que exceções como <code>NoMethodError: undefined method [] for nil:NilClass</code> sejam lançadas. Com os métodos <code>Array#dig</code> e <code>Hash#dig</code> sua vida se tornou muito mais simples, sem a necessidade de usar uma biblioteca externa.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="p">{</span><span class="ss">b: </span><span class="p">{</span><span class="ss">c: </span><span class="s2">"hello"</span><span class="p">}}}</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:b</span><span class="p">,</span> <span class="ss">:c</span><span class="p">)</span>
<span class="c1">#=&gt; "hello"</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:e</span><span class="p">,</span> <span class="ss">:f</span><span class="p">)</span>
<span class="c1">#=&gt; nil</span>

<span class="n">array</span> <span class="o">=</span> <span class="p">[[[</span><span class="s2">"hello"</span><span class="p">]]]</span>

<span class="n">array</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="c1">#=&gt; "hello"</span>

<span class="n">array</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="c1">#=&gt; nil</span>
</code></pre></div>
<p>O mais interessante é que você pode, obviamente, usar isso para navegar uma estrutura mais complexa composta por arrays e hashes, como aquelas retornas por APIs. Veja a diferença para pegar o nome do primeiro usuário da estrutura à seguir:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">data</span> <span class="o">=</span> <span class="p">{</span><span class="ss">users: </span><span class="p">[{</span><span class="ss">name: </span><span class="s2">"John Doe"</span><span class="p">}]}</span>

<span class="c1"># ruby-2.2</span>
<span class="n">data</span><span class="p">[</span><span class="ss">:users</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="n">data</span><span class="p">[</span><span class="ss">:users</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="n">data</span><span class="p">[</span><span class="ss">:users</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="ss">:name</span><span class="p">]</span>
<span class="c1">#=&gt; "John Doe"</span>

<span class="c1"># ruby-2.3</span>
<span class="n">data</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="ss">:users</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">:name</span><span class="p">)</span>
<span class="c1">#=&gt; "John Doe"</span>
</code></pre></div>
<p>Vale lembrar que outras classes também implementam o método <code>dig</code>, como <code>Struct</code> e <code>OpenStruct</code>.</p>

<p>Tickets: <a href="https://redmine.ruby-lang.org/issues/11643">#11643</a> e <a href="https://redmine.ruby-lang.org/11688">#11688</a>.</p>

<h2>Hash#to_proc</h2>

<p>A classe <code>Hash</code> agora implementa o método <code>Hash#to_proc</code>, extremamente útil para os casos onde você precisa pegar todos os valores dada uma lista de chaves.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">2</span><span class="p">,</span> <span class="ss">c: </span><span class="mi">3</span><span class="p">}</span>
<span class="n">wanted_keys</span> <span class="o">=</span> <span class="p">[</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">]</span>

<span class="c1"># ruby-2.2</span>
<span class="n">wanted_keys</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">key</span><span class="o">|</span> <span class="nb">hash</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="p">}</span>
<span class="c1">#=&gt; [1, 3]</span>

<span class="c1"># ruby-2.3</span>
<span class="n">wanted_keys</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="nb">hash</span><span class="p">)</span>
<span class="c1">#=&gt; [1, 3]</span>
</code></pre></div>
<p>Para entender como isso funciona, vamos ver como seria uma possível implementação de <code>Hash#to_proc</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Hash</span>
  <span class="k">def</span> <span class="nf">to_proc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
    <span class="nb">proc</span> <span class="p">{</span><span class="o">|</span><span class="n">key</span><span class="o">|</span> <span class="nb">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="p">}</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="nb">hash</span><span class="p">.</span><span class="nf">to_proc</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="ss">:a</span><span class="p">)</span>
<span class="c1">#=&gt; 1</span>
</code></pre></div>
<ol>
<li>Toda vez que um objeto precisa ser convertido em <code>Proc</code>, o método <code>to_proc</code> é executado. Seria o equivalente a converter o objeto em bloco. Isso é feito implicitamente quando usamos <code>array.map(&amp;hash)</code>, por exemplo. </li>
<li>O método que recebe essa <code>Proc</code> irá, por sua vez, executá-la passando cada um dos itens do array como sendo o argumento da execução, como em <code>hash.to_proc.call(:a)</code>.</li>
<li>A <code>Proc</code> recebe a chave e retorna o valor associado a ela.</li>
</ol>

<p>A mesma ideia se aplica na implementação de <code>Symbol#to_proc</code>, ao qual já estamos acostumados. Uma possível implementação pode ser vista à seguir:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">Symbol</span>
  <span class="k">def</span> <span class="nf">to_proc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
    <span class="nb">proc</span> <span class="p">{</span><span class="o">|</span><span class="n">target</span><span class="o">|</span> <span class="n">target</span><span class="p">.</span><span class="nf">public_send</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="p">}</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="ss">:upcase</span><span class="p">.</span><span class="nf">to_proc</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">)</span>
<span class="c1">#=&gt; "HELLO"</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11653">#11653</a>.</p>

<h2>Hash#fetch_values</h2>

<p>O método <code>Hash#fetch_values</code> retorna o valor de diversas chaves de uma só vez. Caso uma das chaves fornecidas não exista, a exceção <code>KeyError</code> será lançada. Essa é a principal diferença em relação ao já existente <code>Hash#values_at</code>, que retorna <code>nil</code> para chaves não encontradas.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">hash</span> <span class="o">=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">2</span><span class="p">,</span> <span class="ss">c: </span><span class="mi">3</span><span class="p">}</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">values_at</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">)</span>
<span class="c1">#=&gt; [1, 3]</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">fetch_values</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:c</span><span class="p">)</span>
<span class="c1">#=&gt; [1, 3]</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">values_at</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">)</span>
<span class="c1">#=&gt; [1, nil]</span>

<span class="nb">hash</span><span class="p">.</span><span class="nf">fetch_values</span><span class="p">(</span><span class="ss">:a</span><span class="p">,</span> <span class="ss">:d</span><span class="p">)</span>
<span class="c1">#=&gt; KeyError: key not found: :d</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/10017">#10017</a>.</p>

<h2>Comparação de hashes</h2>

<p>Se você já precisou fazer comparações entre hashes, deve ter tentado usar o método <code>Hash#include?</code>. Imagine que você quer saber se o hash <code>a</code> contém o hash <code>b</code>. Parece natural fazer algo como <code>a.include?(b)</code>. Acontece que o método <code>Hash#include?</code> não passa de um alias para o método <code>Hash#has_key?</code> e irá apenas verificar se a chave é existente ou não.</p>

<p>Para resolver este problema, agora podemos usar alguns os métodos <code>Hash#&gt;</code>, <code>Hash#&gt;=</code>, <code>Hash#&lt;</code> e <code>Hash#&lt;=</code> para fazer este tipo de comparação.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># left é exatamente igual a right?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="o">==</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>

<span class="c1"># left contém e tem mais itens que right?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">1</span><span class="p">}</span> <span class="o">&gt;</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>

<span class="c1"># left contém right e, opcionalmente, possui mais itens?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="o">&gt;=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>

<span class="c1"># left contém right e possui menos itens?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="o">&lt;</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">b: </span><span class="mi">2</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>

<span class="c1"># left contém right e, opcionalmente, possui menos itens?</span>
<span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span> <span class="o">&lt;=</span> <span class="p">{</span><span class="ss">a: </span><span class="mi">1</span><span class="p">}</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>Note que estes novos métodos não interferem na ordenação de hashes, já que o método <code>Hash#&lt;=&gt;</code> permaneceu inalterado.</p>

<p>A proposta original utilizava o método <code>Hash#contain?</code>, mas o Matz achou que este nome era ambíguo (embora ele não tenha especificado, acredito que é por causa do próprio <code>Hash#include?</code>).</p>

<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/10984">#10984</a>.</p>

<h2>Enumerable#grep_v</h2>

<p>O método <code>Enumerable#grep_v</code> é a contra-partida do método <code>Enumerable#grep</code> e emula o comportamento do comando <code>grep -v</code>, ignorando todos os itens que casarem a condição. No exemplo à seguir, estamos ignorando todas as cores que tiverem a letra <code>w</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">colors</span> <span class="o">=</span> <span class="sx">%w[white black brown]</span>

<span class="n">colors</span><span class="p">.</span><span class="nf">grep_v</span><span class="p">(</span><span class="sr">/w/</span><span class="p">)</span>
<span class="c1">#=&gt; ["black"]</span>
</code></pre></div>
<p>Tickets: <a href="https://redmine.ruby-lang.org/issues/11049">#11049</a> e <a href="https://redmine.ruby-lang.org/issues/11773">#11773</a>.</p>

<h2>Números positivos e negativos</h2>

<p>Sugerido pelo <a href="http://twitter.com/rafaelfranca">Rafael França</a> após serem adicionados ao <a href="https://github.com/rails/rails/commit/e54277a45da3c86fecdfa930663d7692fd083daa">ActiveSupport</a>, estes métodos permitem verificar se um número é positivo ou não, seguindo a mesma linha dos métodos <code>Numeric#zero?</code> e <code>Numeric#nonzero?</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="mi">1</span><span class="p">.</span><span class="nf">positive?</span>
<span class="c1">#=&gt; true</span>

<span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="nf">negative?</span>
<span class="c1">#=&gt; trues</span>

<span class="mi">0</span><span class="p">.</span><span class="nf">negative?</span>
<span class="c1">#=&gt; false</span>

<span class="mi">0</span><span class="p">.</span><span class="nf">positive?</span>
<span class="c1">#=&gt; false</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11151">#11151</a>.</p>

<h2>Module.deprecate_constant</h2>

<p>O método <code>Module.deprecate_constant</code> permite emitir um aviso quando uma constante é acessada.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">module</span> <span class="nn">A</span>
  <span class="k">module</span> <span class="nn">B</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="no">A</span><span class="p">.</span><span class="nf">deprecate_constant</span><span class="p">(</span><span class="ss">:B</span><span class="p">)</span>

<span class="no">A</span><span class="o">::</span><span class="no">B</span>
<span class="c1">#=&gt; warning: constant A::B is deprecated</span>
</code></pre></div>
<p>Um problema desta nova funcionalidade é que não é possível especificar qual é a alternativa para esta constante, caso haja uma. Idealmente deveria ser possível fazer algo como <code>A.deprecate_constant(:B, A::C)</code> para emitir o aviso <code>warning: constant A::B is deprecated; use A::C instead.</code>; se nenhuma alternativa fosse fornecida, exibiria a mensagem do exemplo anterior.</p>

<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11398">#11398</a>.</p>

<h2>Did You Mean</h2>

<p>O Ruby 2.3 agora possui integração nativa com a gem <a href="https://github.com/yuki24/did_you_mean">did_you_mean</a>, que melhora as mensagens de erros para casos de nomes incorretos de classes, métodos e variáveis (<em>typos</em>).</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># ruby 2.2</span>
<span class="no">Strin</span>
<span class="c1">#=&gt; NameError: uninitialized constant Strin</span>

<span class="c1"># ruby 2.3</span>
<span class="no">Strin</span>
<span class="c1">#=&gt; NameError: uninitialized constant Strin</span>
<span class="c1">#=&gt; Did you mean?  String</span>
<span class="c1">#=&gt;                STDIN</span>
</code></pre></div>
<p>O mesmo acontece com nomes de variáveis e métodos.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">users</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1">#=&gt; []</span>

<span class="n">user</span>
<span class="c1">#=&gt; NameError: undefined local variable or method `user' for main:Object</span>
<span class="c1">#=&gt; Did you mean?  users</span>

<span class="n">users</span><span class="p">.</span><span class="nf">revers</span>
<span class="c1">#=&gt; NoMethodError: undefined method `revers' for []:Array</span>
<span class="c1">#=&gt; Did you mean?  reverse</span>
<span class="c1">#=&gt;                reverse!</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/11252">#11252</a>.</p>

<h2>Heredoc Strings</h2>

<p>O Ruby possui suporte para strings multilinhas. No entanto, em muitos casos é comum usarmos strings <em>heredoc</em>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">sql</span> <span class="o">=</span> <span class="o">&lt;&lt;</span><span class="no">SQL</span><span class="sh">
SELECT * 
FROM users
WHERE user_id = 1
</span><span class="no">SQL</span>
</code></pre></div>
<p>Perceba que no exemplo anterior, você precisa obrigatoriamente adicionar o delimitador <code>SQL</code> no começo da linha. Este delimitador pode ter o nome que você quiser e, em alguns editores como o Sublime Text, irá ativar o syntax highlighting.</p>

<p>Você também pode usar um outro tipo de heredoc que permite indentar o delimitador. Ele é especialmente útil quando você possui métodos.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">UserFinder</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">query</span>
    <span class="n">sql</span> <span class="o">=</span> <span class="o">&lt;&lt;-</span><span class="no">SQL</span><span class="sh">
      SELECT * 
      FROM users
      WHERE user_id = 1
</span><span class="no">    SQL</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>A maior desvantagem do <code>&lt;&lt;-</code> é que ele irá adicionar os espaçamentos da indentação na string. Isso significa que se você fizer o output daquela string, terá algo como</p>
<div class="highlight"><pre class="highlight plaintext"><code>      SELECT * 
      FROM users
      WHERE user_id = 1
</code></pre></div>
<p>em vez de</p>
<div class="highlight"><pre class="highlight plaintext"><code>SELECT * 
FROM users
WHERE user_id = 1
</code></pre></div>
<p>Se você tem acesso à biblioteca ActiveSupport, pode usar o método <code>String#strip_heredoc</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">UserFinder</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">query</span>
    <span class="n">sql</span> <span class="o">=</span> <span class="o">&lt;&lt;-</span><span class="no">SQL</span><span class="p">.</span><span class="nf">strip_heredoc</span><span class="sh">
      SELECT * 
      FROM users
      WHERE user_id = 1
</span><span class="no">    SQL</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>À partir do Ruby 2.3 você não precisa mais do método <code>String#strip_heredoc</code> e agora tem um novo tipo de heredoc. Trata-se de <code>&lt;&lt;~</code>, que ignora todos os espaçamentos da indentação, mantendo as quebras de linha.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">UserFinder</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">query</span>
    <span class="n">sql</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">SQL</span><span class="sh">
      SELECT * 
      FROM users
      WHERE user_id = 1
</span><span class="no">    SQL</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>Ticket: <a href="https://redmine.ruby-lang.org/issues/9098">#9098</a>.</p>

<h2>Finalizando</h2>

<p>O Ruby vem melhorando incrementalmente ao longo dos anos. Depois da migração mais complicada de 1.8 para 1.9, não tivemos tantas mudanças drásticas que dificultaram a migração. E isso é uma característica importante para que a adoção de novas versões seja grande. Além disso, cada versão lançada tem tido melhorias de performance de 5 a 10% em comparação à versão anterior.</p>

<p>Para saber quais são os planos para o Ruby, recomendo que você assista o <a href="https://www.youtube.com/watch?v=LE0g2TUsJ4U">keynote do Matz</a> apresentado na Rubyconf deste ano. Dentre os objetivos está melhorar o suporte para multi-cores e programação concorrente, tornando a versão 3.0 três vezes mais rápida que a versão 2.0 em uma iniciativa chamada de Ruby3x3. O futuro do Ruby ainda é promissor.</p>

<div class="footnotes">
<hr>
<ol>

<li id="fn1">
<p><a href="http://www.schneems.com">Richard Schneeman</a> conseguiu 12% em um patch do Ruby on Rails onde a estratégia é, basicamente, utilizar strings imutáveis.&nbsp;<a href="#fnref1">&#8617;</a></p>
</li>

<li id="fn2">
<p>Lembre-se que as opções do interpretador Ruby também podem ser passadas através da variável de ambiente <code>RUBYOPT</code>. Sendo assim, podemos fazer algo como <code>RUBYOPT=&#39;--enable-frozen-string-literal&#39; ruby -e &#39;puts &quot;Ruby&quot;.frozen?&#39;</code>.&nbsp;<a href="#fnref2">&#8617;</a></p>
</li>

</ol>
</div>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/o-que-mudou-no-ruby-dois-ponto-tres</link>
      <guid>https://nandovieira.com.br/o-que-mudou-no-ruby-dois-ponto-tres</guid>
      <pubDate>Sun, 13 Dec 2015 16:42:00 -0200</pubDate>
    </item>
    <item>
      <title>Trabalhando com datas no Ruby on Rails</title>
      <description>
        <![CDATA[<p>Trabalhar com datas pode ser uma tarefa mais complexa do que você imagina. É preciso levar em consideração o fuso horário, saber como vai armazenar essas datas no banco de dados, entender como converter datas fornecidas pelo usuário ou enviar datas formatadas. Ah, tem também o horário de verão.</p>

<p>Neste artigo veremos quais as principais dificuldades de se trabalhar com datas e como usar os diversos utilitários disponibilizados pelo Ruby e Rails para garantir que seu sistema usa corretamente as datas.</p>

<h2>Como funciona no Ruby</h2>

<p>O Ruby possui duas classes para lidar com datas: <code>Date</code> e <code>Time</code><sup id="fnref1"><a href="#fn1">1</a></sup>. Com elas é possível gerar datas usando strings que são interpretadas por estas classes ou com atributos individuais que identificam o ano, hora, mês, etc.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">require</span> <span class="s2">"time"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"Dec 8 2015 10:19"</span><span class="p">)</span>
<span class="c1">#=&gt; 2015-12-08 10:19:00 -0200</span>

<span class="no">Date</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"Dec 8 2015"</span><span class="p">)</span>
<span class="c1">#=&gt; 2015-12-08</span>
</code></pre></div>
<p>Todo o cálculo de dados deve ser feito de forma manual. Por exemplo, para avançar uma hora, você pode somar 3600 segundos à data atual.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">time</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="c1">#=&gt; 2015-12-08 10:26:40 -0200</span>

<span class="n">time</span> <span class="o">+</span> <span class="mi">3600</span>
<span class="c1">#=&gt; 2015-12-08 11:26:40 -0200</span>
</code></pre></div>
<p>Algumas operações podem ser mais trabalhosas (não difíceis), mas de um modo geral esses cálculos são simples de serem feitos. O mesmo não pode ser dito de cálculos que envolvem o fuso horário.</p>

<h3>Trabalhando com fuso horário</h3>

<p>A definição de fuso horário é, sem dúvida nenhuma, a parte mais complicada ao se trabalhar com datas. Primeiro, você precisa entender como o Ruby funciona.</p>

<p>No Ruby, a definição de fuso horário é feita exclusivamente pela variável de ambiente <code>TZ</code>. Na maioria das vezes, essa variável de ambiente não é definida, mas isso vai depender de como seu servidor foi configurado; sistemas POSIX mais antigos usavam esta variável para configurar o fuso horário, mas hoje isso é feito através do arquivo <code>/etc/localtime</code>.</p>

<p>Veja como o Ruby se comporta com e sem a variável <code>TZ</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">ENV</span><span class="p">[</span><span class="s2">"TZ"</span><span class="p">]</span>
<span class="c1">#=&gt; nil</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="c1">#=&gt; 2015-12-08 10:30:00 -0200</span>

<span class="no">ENV</span><span class="p">[</span><span class="s2">"TZ"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"America/Los_Angeles"</span>
<span class="c1">#=&gt; "America/Los_Angeles"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="c1">#=&gt; 2015-12-08 04:30:14 -0800</span>
</code></pre></div>
<p>O comando <code>date</code>, disponível em sistemas <em>*nix</em>, também usa a variável <code>TZ</code>, caso ela esteja disponível, para exibir a data na sessão de um usuário.</p>
<div class="highlight"><pre class="highlight plaintext"><code>$ date
Tue Dec  8 09:37:12 BRST 2015

$ export TZ=America/Los_Angeles

$ date
Tue Dec  8 03:37:27 PST 2015
</code></pre></div>
<p>O maior problema é que nem todo software irá utilizar esta variável automaticamente. No PostgreSQL, por exemplo, mesmo que a variável <code>TZ</code> esteja definida, ele irá retornar o valor definido pela configuração <code>timezone</code><sup id="fnref2"><a href="#fn2">2</a></sup>.</p>
<div class="highlight"><pre class="highlight sql"><code><span class="k">SELECT</span> <span class="n">current_setting</span><span class="p">(</span><span class="s1">'timezone'</span><span class="p">);</span>
<span class="o">#=&gt;</span> <span class="n">Brazil</span><span class="o">/</span><span class="n">East</span>
</code></pre></div>
<p>A melhor maneira de evitar de lidar com fuso horário nas diferentes peças de sua infraestrutura é simplesmente ignorando-o; em vez de definir um fuso horário em particular, use <a href="http://yellerapp.com/posts/2015-01-12-the-worst-server-setup-you-can-make.html">Etc/UTC</a>. Isso evitará problemas comuns relacionados a horário de verão em cron jobs, além de simplificar o modo como diferentes aplicações irão trabalhar com datas e conversar entre si. Deixe toda a formatação de fuso horário ser responsabilidade da camada da aplicação.</p>

<h2>Como funciona no Ruby on Rails</h2>

<p>Para definir o fuso horário de sua aplicação, utilize o arquivo de ambiente ou crie um arquivo de inicialização.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># config/initializers/time_zone.rb</span>
<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="o">=</span> <span class="s2">"America/Sao_Paulo"</span>
</code></pre></div>
<p>O Ruby on Rails permite que você defina o fuso horário de sua aplicação, independente do valor definido na variável <code>TZ</code> e isso traz algumas implicações.</p>

<p>Primeiro, o Ruby desconhece totalmente o fuso horário definido pelo Rails. Isso significa que se meu sistema estiver definido como <code>America/Sao_Paulo</code> e meu fuso horário estiver como <code>America/Los_Angeles</code>, terei datas diferentes dependendo de como eu gere essas datas.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">zone</span>
<span class="c1">#=&gt; "BRST"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="o">=</span> <span class="s2">"America/Los_Angeles"</span>
<span class="c1">#=&gt; "America/Los_Angeles"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">zone</span>
<span class="c1">#=&gt; "BRST"</span>
</code></pre></div>
<p>Para que você gere datas que tenha conhecimento sobre o fuso horário, é preciso usar os métodos definidos pelo ActiveSupport, que são objetos gerados à partir da classe <code>ActiveSupport::TimeWithZone</code><sup id="fnref3"><a href="#fn3">3</a></sup>. São diversos métodos e seu uso vai depender do que você precisa.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">now</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 03:37:57 PST -08:00</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">today</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">current</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 03:38:17 PST -08:00</span>

<span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">.</span><span class="nf">ago</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 02:38:28 PST -08:00</span>

<span class="mi">1</span><span class="p">.</span><span class="nf">day</span><span class="p">.</span><span class="nf">from_now</span>
<span class="c1">#=&gt; Wed, 09 Dec 2015 03:38:36 PST -08:00</span>

<span class="no">Date</span><span class="p">.</span><span class="nf">yesterday</span>
<span class="c1">#=&gt; Mon, 07 Dec 2015</span>

<span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span>
<span class="c1">#=&gt; Wed, 09 Dec 2015</span>
</code></pre></div>
<p>De um modo geral:</p>

<ul>
<li>Use <code>Time.current</code> em vez de <code>Time.now</code>.</li>
<li>Use <code>Date.current</code> em vez de <code>Date.today</code>.</li>
</ul>

<p>É importante dizer que <code>Time.current</code> pode ignorar informações de fuso horário caso esta configuração não esteja definida; o mesmo é válido para <code>Date.current</code>. Veja a implementação destes métodos na biblioteca ActiveSupport:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># activesupport-4.2.5/lib/active_support/core_ext/time/calculations.rb</span>
<span class="c1"># line 30</span>
<span class="k">class</span> <span class="nc">Time</span>
  <span class="k">def</span> <span class="nf">current</span>
    <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="p">?</span> <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">now</span> <span class="p">:</span> <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="c1"># activesupport-4.2.5/lib/active_support/core_ext/date/calculations.rb</span>
<span class="c1"># line 46</span>
<span class="k">class</span> <span class="nc">Date</span>
  <span class="k">def</span> <span class="nf">current</span>
    <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="p">?</span> <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">today</span> <span class="p">:</span> <span class="o">::</span><span class="no">Date</span><span class="p">.</span><span class="nf">today</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>Você pode usar <code>Time.zone.today</code> e <code>Time.zone.now</code> para ter um resultado determinístico.</p>

<h3>Lidando com o horário de verão</h3>

<p>O ActiveSupport utiliza a gem <a href="https://rubygems.org/gems/tzinfo">TZInfo</a> para ter conhecimento sobre os diversos fusos horários. Para isso, essa gem carrega diversas informações do sistema operacional; no Ubuntu, essas informações são disponibilizadas através do pacote <code>tzdata</code>.</p>

<p>Se você mantém o seu servidor atualizado, isso significa que a aplicação terá acesso às informações sobre quando começa ou termina o <a href="https://pt.wikipedia.org/wiki/Hor%C3%A1rio_de_ver%C3%A3o">Horário de Verão</a> por todo o mundo.</p>

<p>Veja, por exemplo, como fica o Horário de Verão deste ano<sup id="fnref4"><a href="#fn4">4</a></sup>:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'2015-10-17'</span><span class="p">).</span><span class="nf">dst?</span>
<span class="c1">#=&gt; false</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'2015-10-18'</span><span class="p">).</span><span class="nf">dst?</span>
<span class="c1">#=&gt; true</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'2016-02-20'</span><span class="p">).</span><span class="nf">dst?</span>
<span class="c1">#=&gt; true</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'2016-02-21'</span><span class="p">).</span><span class="nf">dst?</span>
<span class="c1">#=&gt; false</span>
</code></pre></div>
<p>Às vezes você precisará fazer a conversão e o horário de verão pode retornar um horário diferente. Recentemente precisei integrar a geração de datas com um calendário. A maior dificuldade foi em relação à exibição de uma data futura, levando em consideração o fuso horário local. </p>

<p>Acontece que a solução é bastante simples; basta não definir o fuso horário na string que será interpretada, deixando isso para o método <code>Time.use_zone</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">current</span>
<span class="c1">#=&gt; Mon, 07 Dec 2015 18:57:51 BRST -02:00</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">use_zone</span><span class="p">(</span><span class="s2">"America/Sao_Paulo"</span><span class="p">)</span> <span class="k">do</span>
  <span class="n">starts_at</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2016-03-05 10:00"</span><span class="p">)</span>
  <span class="c1">#=&gt; Sat, 05 Mar 2016 10:00:00 BRT -03:00</span>

  <span class="n">ends_at</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2016-03-05 17:00"</span><span class="p">)</span>
  <span class="c1">#=&gt; Sat, 05 Mar 2016 17:00:00 BRT -03:00</span>
<span class="k">end</span>
</code></pre></div>
<h3>Como as datas são persistidas</h3>

<p>O ActiveRecord é configurado por padrão para persistir as datas como <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">UTC</a>. Isso é definido pelas configurações <code>ActiveRecord::Base.default_timezone</code> e <code>ActiveRecord::Base.time_zone_aware_attributes</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">default_timezone</span>
<span class="c1">#=&gt; :utc</span>

<span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">time_zone_aware_attributes</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>Isso significa que toda vez que você define uma data para um atributo do ActiveRecord, esta data será armazenada como UTC. Ao carregar o registro, o Rails irá converter a data de volta para o fuso horário definido na aplicação<sup id="fnref5"><a href="#fn5">5</a></sup>.</p>

<p>Perceba que que a data pode ser armazenada incorretamente caso você utilize os métodos <code>Date.today</code> ou <code>Time.now</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="o">=</span> <span class="s2">"America/Los_Angeles"</span>
<span class="c1">#=&gt; "America/Los_Angeles"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">.</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-08 02:00:00 UTC</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">current</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">.</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-08 08:00:00 UTC</span>
</code></pre></div>
<p>Essa diferença acontece porque <code>Time.now</code> ignora o fuso horário definido pela aplicação e está usando o fuso horário local do sistema (que neste caso é <code>BRST-0200</code>). Por isso é muito importante que você use uma data que tenha conhecimento do fuso horário, como mostrado anteriormente.</p>

<p>O Rails assume que todas as datas armazenadas no banco de dados estão em UTC. Se alguém adicionar um registro por fora do sistema sem considerar o fuso horário pode ter uma diferença de algumas horas na hora que esse registro for carregado na aplicação. O PostgreSQL possui um campo que tem conhecimento do fuso e que fará a conversão para você antes de persistir a informação. Infelizmente, o Rails não tem suporte nativo para <a href="http://www.postgresql.org/docs/current/static/datatype-datetime.html"><code>time with time zone</code></a>, mas você pode adicioná-lo facilmente; basta criar um arquivo de inicialização que contenha o seguinte código:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># config/initializers/active_record.rb</span>
<span class="no">ActiveRecord</span><span class="o">::</span><span class="no">ConnectionAdapters</span><span class="o">::</span><span class="no">PostgreSQLAdapter</span><span class="o">::</span>
  <span class="no">NATIVE_DATABASE_TYPES</span><span class="p">[</span><span class="ss">:datetime</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="ss">name: </span><span class="s2">"timestamp with time zone"</span><span class="p">}</span>
</code></pre></div>
<h3>Fazendo consultas de banco de dados</h3>

<p>Como o Rails considera que todas as datas são armazenas como UTC, a única coisa que você precisa garantir é que as datas estejam definidas com a informação de fuso horário (<code>Time.current</code> ou <code>Date.current</code>, por exemplo) e o framework irá se encarregar de fazer a conversão correta.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Article</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"published_at &gt;= ?"</span><span class="p">,</span> <span class="no">Time</span><span class="p">.</span><span class="nf">current</span><span class="p">)</span>
</code></pre></div>
<p>Lembre-se que definição de datas como <code>1.day.ago</code> e <code>Date.current.beginning_of_month</code>, dentre outros, irão gerar objetos que carregam o fuso horário, então você não terá problemas.</p>

<h3>Interpretando datas enviadas pelos usuários</h3>

<p>Os mesmos cuidados valem na hora de converter uma string em um objeto de data. O Ruby possui os métodos <code>Time.parse</code> e <code>Date.parse</code>, mas eles ignorarão as informações de fuso horário. Por isso, use <code>Time.zone.parse</code> sempre que precisar fazer esta conversão.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"8:47am Dec 7th, 2015"</span><span class="p">).</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-07 10:47:00 UTC</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"8:47am Dec 7th, 2015"</span><span class="p">).</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-07 16:47:00 UTC</span>
</code></pre></div>
<p>Para o caso de você receber diversos números para cada item que compõe uma data, utilize o método <code>Time.zone.local</code> em vez de <code>Time.new</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">2015</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">0</span><span class="p">).</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-07 10:47:00 UTC</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">local</span><span class="p">(</span><span class="mi">2015</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">0</span><span class="p">).</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-07 16:47:00 UTC</span>
</code></pre></div>
<p>Finalmente, se você tiver um timestamp, use <code>Time.zone.at</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">at</span><span class="p">(</span><span class="mi">1449506820</span><span class="p">)</span>
<span class="c1">#=&gt; 2015-12-07 16:47:00 UTC</span>
</code></pre></div>
<p>Para definir um fuso horário apenas durante uma execução qualquer, podemos usar o método <code>Time.use_zone</code>. Ele irá definir o fuso horário especificado apenas para a execução do bloco.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="o">=</span> <span class="s2">"America/Sao_Paulo"</span>
<span class="c1">#=&gt; "America/Sao_Paulo"</span>

<span class="n">now_in_sao_paulo</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">current</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 09:40:31 BRST -02:00</span>

<span class="n">now_in_los_angeles</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">use_zone</span><span class="p">(</span><span class="s2">"America/Los_Angeles"</span><span class="p">)</span> <span class="k">do</span>
  <span class="no">Time</span><span class="p">.</span><span class="nf">current</span>
<span class="k">end</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 03:40:31 PST -08:00</span>

<span class="n">now_in_sao_paulo</span><span class="p">.</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-08 11:40:31 UTC</span>

<span class="n">now_in_los_angeles</span><span class="p">.</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-08 11:40:31 UTC</span>

<span class="n">now_in_sao_paulo</span><span class="p">.</span><span class="nf">to_i</span> <span class="o">==</span> <span class="n">now_in_los_angeles</span><span class="p">.</span><span class="nf">to_i</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>Esse método pode ser usado no controller, caso você precise definir o fuso horário especificado pelo usuário.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">ApplicationController</span> <span class="o">&lt;</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">Base</span>
  <span class="n">around_action</span> <span class="ss">:set_timezone</span><span class="p">,</span> <span class="ss">if: :logged_in?</span>

  <span class="kp">private</span>

  <span class="k">def</span> <span class="nf">set_timezone</span><span class="p">(</span><span class="o">&amp;</span><span class="n">action</span><span class="p">)</span>
    <span class="no">Time</span><span class="p">.</span><span class="nf">use_zone</span><span class="p">(</span><span class="n">current_user</span><span class="p">.</span><span class="nf">time_zone</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">action</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<h3>Trabalhando com fuso horário no JavaScript</h3>

<p>A melhor maneira de se trabalhar com datas e fuso horário no JavaScript é usar a biblioteca <a href="http://momentjs.com">moment.js</a>.</p>

<p>Para enviar uma data do Ruby para o JavaScript, utilize o método <code>Time#iso8601</code>. Ele irá retornar uma formatação como <code>2015-12-07T19:25:45Z</code>. Com o moment.js, você pode fazer a conversão desse formato para o objeto <code>Date</code> do JavaScript.</p>
<div class="highlight"><pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">date</span> <span class="o">=</span> <span class="nf">moment</span><span class="p">(</span><span class="dl">"</span><span class="s2">2015-12-07T19:25:45Z</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">//=&gt; Mon Dec 07 2015 17:25:45 GMT-0200 (BRST)</span>

<span class="nx">date</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span><span class="dl">"</span><span class="s2">MMM DD, YYYY - h:mma</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">//=&gt; Dec 07, 2015 - 5:25pm</span>
</code></pre></div>
<p>No exemplo acima ele fez a conversão automática para o fuso horário identificado pelo navegador, no meu caso <code>BRST-0200</code>. Se você precisar fazer conversão para outro fuso horário, use o <a href="http://momentjs.com/timezone">momentjs-timezone</a>.</p>

<h2>Finalizando</h2>

<p>Se o seu aplicativo usa datas, principalmente quando há cálculo envolvido, entender como funciona o sistema de datas do Rails é essencial. Para facilitar a sua vida, basta seguir as regras abaixo:</p>

<ul>
<li>Defina o fuso horário de seus servidores como <code>Etc/UTC</code>.</li>
<li>Defina sempre o fuso horário em sua aplicação Ruby on Rails, mesmo que seja <code>Etc/UTC</code>.</li>
<li>Lide com a apresentação do fuso horário na camada da aplicação.</li>
<li>Use os métodos <code>Time.current</code> e <code>Date.current</code> para pegar as datas atuais.</li>
<li>Use <code>Time.zone.parse</code> para converter strings em datas.</li>
<li>Converta <code>Date</code> em <code>ActiveSupport::TimeWithZone</code> para fazer comparações, como em <code>date.in_time_zone == Time.current.beginning_of_day</code>.</li>
</ul>

<h2>Resources</h2>

<ul>
<li><a href="http://brendankemp.com/essays/dealing-with-time-zones-using-rails-and-postgres/">Dealing With Time Zones Using Rails and Postgres</a></li>
<li><a href="https://www.reinteractive.net/posts/168-dealing-with-timezones-effectively-in-rails">Dealing with timezones effectively in Rails</a></li>
<li><a href="http://alwayscoding.ca/momentos/2013/08/22/handling-dates-and-timezones-in-ruby-and-rails/">Handling Dates &amp; Timezones in Ruby &amp; Rails</a></li>
<li><a href="http://brendankemp.com/essays/handling-time-zones-in-rails/">Handling Time Zones in Rails</a></li>
<li><a href="http://makandracards.com/makandra/646-how-rails-and-mysql-are-handling-time-zones">How Rails and MySQL are handling time zones</a></li>
<li><a href="http://danilenko.org/2012/7/6/rails_timezones/">The Exhaustive Guide to Rails Time Zones</a></li>
<li><a href="http://yellerapp.com/posts/2015-01-12-the-worst-server-setup-you-can-make.html">The Worst Server Setup Mistake You Can Make</a></li>
<li><a href="http://www.elabs.se/blog/36-working-with-time-zones-in-ruby-on-rails">Working with time zones in Ruby on Rails</a></li>
</ul>

<div class="footnotes">
<hr>
<ol>

<li id="fn1">
<p>Existe uma terceira classe chamada <code>DateTime</code>, que é apenas uma subclasse de <code>Date</code> com conhecimento sobre os atributos de hora. Em versões antigas do Ruby, a classe <code>Time</code> tinha limitações no intervalo de datas em sistemas 32-bit; nestas situações, o uso de <code>DateTime</code> era recomendado. À partir do Ruby 1.9.2 essa limitação foi removida e a classe <code>Time</code> pode agora trabalhar com qualquer intervalo de datas. Além disso, as classes <code>Time</code> e <code>Date</code> tem <a href="https://gist.github.com/fnando/54ddc21c4640d09c30ce">performance semelhante</a>.&nbsp;<a href="#fnref1">&#8617;</a></p>
</li>

<li id="fn2">
<p>O PostgreSQL só irá usar a variável de ambiente <code>TZ</code> caso a configuração <code>timezone</code> não tenha sido especificada no arquivo <code>postgresql.conf</code>. No entanto, como ela é definida com um valor padrão, é muito improvável que o PostgreSQL use a variável <code>TZ</code> a não ser que você tenha essa intenção.&nbsp;<a href="#fnref2">&#8617;</a></p>
</li>

<li id="fn3">
<p>Os métodos que retornam datas, como <code>Time.zone.today</code>, <code>Date.yesterday</code> e <code>Date.tomorrow</code> retornam objetos da classe <code>Date</code> e, por este motivo, não possuem conhecimento sobre fuso horário. Se precisar, converta estes objetos para um objeto do tipo <code>ActiveSupport::TimeWithZone</code> com <code>date.in_time_zone</code>.&nbsp;<a href="#fnref3">&#8617;</a></p>
</li>

<li id="fn4">
<p>O <a href="https://pt.wikipedia.org/wiki/Lista_de_per%C3%ADodos_em_que_vigorou_o_hor%C3%A1rio_de_ver%C3%A3o_no_Brasil">horário de verão 2016</a> vai de 18 de outubro de 2015 a 21 de fevereiro de 2016.&nbsp;<a href="#fnref4">&#8617;</a></p>
</li>

<li id="fn5">
<p>A configuracão <code>ActiveRecord::Base.time_zone_aware_attributes</code> é desativada por padrão se você está usando o ActiveRecord fora do Ruby on Rails.&nbsp;<a href="#fnref5">&#8617;</a></p>
</li>

</ol>
</div>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p>Trabalhar com datas pode ser uma tarefa mais complexa do que você imagina. É preciso levar em consideração o fuso horário, saber como vai armazenar essas datas no banco de dados, entender como converter datas fornecidas pelo usuário ou enviar datas formatadas. Ah, tem também o horário de verão.</p>

<p>Neste artigo veremos quais as principais dificuldades de se trabalhar com datas e como usar os diversos utilitários disponibilizados pelo Ruby e Rails para garantir que seu sistema usa corretamente as datas.</p>

<h2>Como funciona no Ruby</h2>

<p>O Ruby possui duas classes para lidar com datas: <code>Date</code> e <code>Time</code><sup id="fnref1"><a href="#fn1">1</a></sup>. Com elas é possível gerar datas usando strings que são interpretadas por estas classes ou com atributos individuais que identificam o ano, hora, mês, etc.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">require</span> <span class="s2">"time"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"Dec 8 2015 10:19"</span><span class="p">)</span>
<span class="c1">#=&gt; 2015-12-08 10:19:00 -0200</span>

<span class="no">Date</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"Dec 8 2015"</span><span class="p">)</span>
<span class="c1">#=&gt; 2015-12-08</span>
</code></pre></div>
<p>Todo o cálculo de dados deve ser feito de forma manual. Por exemplo, para avançar uma hora, você pode somar 3600 segundos à data atual.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">time</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="c1">#=&gt; 2015-12-08 10:26:40 -0200</span>

<span class="n">time</span> <span class="o">+</span> <span class="mi">3600</span>
<span class="c1">#=&gt; 2015-12-08 11:26:40 -0200</span>
</code></pre></div>
<p>Algumas operações podem ser mais trabalhosas (não difíceis), mas de um modo geral esses cálculos são simples de serem feitos. O mesmo não pode ser dito de cálculos que envolvem o fuso horário.</p>

<h3>Trabalhando com fuso horário</h3>

<p>A definição de fuso horário é, sem dúvida nenhuma, a parte mais complicada ao se trabalhar com datas. Primeiro, você precisa entender como o Ruby funciona.</p>

<p>No Ruby, a definição de fuso horário é feita exclusivamente pela variável de ambiente <code>TZ</code>. Na maioria das vezes, essa variável de ambiente não é definida, mas isso vai depender de como seu servidor foi configurado; sistemas POSIX mais antigos usavam esta variável para configurar o fuso horário, mas hoje isso é feito através do arquivo <code>/etc/localtime</code>.</p>

<p>Veja como o Ruby se comporta com e sem a variável <code>TZ</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">ENV</span><span class="p">[</span><span class="s2">"TZ"</span><span class="p">]</span>
<span class="c1">#=&gt; nil</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="c1">#=&gt; 2015-12-08 10:30:00 -0200</span>

<span class="no">ENV</span><span class="p">[</span><span class="s2">"TZ"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"America/Los_Angeles"</span>
<span class="c1">#=&gt; "America/Los_Angeles"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="c1">#=&gt; 2015-12-08 04:30:14 -0800</span>
</code></pre></div>
<p>O comando <code>date</code>, disponível em sistemas <em>*nix</em>, também usa a variável <code>TZ</code>, caso ela esteja disponível, para exibir a data na sessão de um usuário.</p>
<div class="highlight"><pre class="highlight plaintext"><code>$ date
Tue Dec  8 09:37:12 BRST 2015

$ export TZ=America/Los_Angeles

$ date
Tue Dec  8 03:37:27 PST 2015
</code></pre></div>
<p>O maior problema é que nem todo software irá utilizar esta variável automaticamente. No PostgreSQL, por exemplo, mesmo que a variável <code>TZ</code> esteja definida, ele irá retornar o valor definido pela configuração <code>timezone</code><sup id="fnref2"><a href="#fn2">2</a></sup>.</p>
<div class="highlight"><pre class="highlight sql"><code><span class="k">SELECT</span> <span class="n">current_setting</span><span class="p">(</span><span class="s1">'timezone'</span><span class="p">);</span>
<span class="o">#=&gt;</span> <span class="n">Brazil</span><span class="o">/</span><span class="n">East</span>
</code></pre></div>
<p>A melhor maneira de evitar de lidar com fuso horário nas diferentes peças de sua infraestrutura é simplesmente ignorando-o; em vez de definir um fuso horário em particular, use <a href="http://yellerapp.com/posts/2015-01-12-the-worst-server-setup-you-can-make.html">Etc/UTC</a>. Isso evitará problemas comuns relacionados a horário de verão em cron jobs, além de simplificar o modo como diferentes aplicações irão trabalhar com datas e conversar entre si. Deixe toda a formatação de fuso horário ser responsabilidade da camada da aplicação.</p>

<h2>Como funciona no Ruby on Rails</h2>

<p>Para definir o fuso horário de sua aplicação, utilize o arquivo de ambiente ou crie um arquivo de inicialização.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># config/initializers/time_zone.rb</span>
<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="o">=</span> <span class="s2">"America/Sao_Paulo"</span>
</code></pre></div>
<p>O Ruby on Rails permite que você defina o fuso horário de sua aplicação, independente do valor definido na variável <code>TZ</code> e isso traz algumas implicações.</p>

<p>Primeiro, o Ruby desconhece totalmente o fuso horário definido pelo Rails. Isso significa que se meu sistema estiver definido como <code>America/Sao_Paulo</code> e meu fuso horário estiver como <code>America/Los_Angeles</code>, terei datas diferentes dependendo de como eu gere essas datas.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">zone</span>
<span class="c1">#=&gt; "BRST"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="o">=</span> <span class="s2">"America/Los_Angeles"</span>
<span class="c1">#=&gt; "America/Los_Angeles"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">zone</span>
<span class="c1">#=&gt; "BRST"</span>
</code></pre></div>
<p>Para que você gere datas que tenha conhecimento sobre o fuso horário, é preciso usar os métodos definidos pelo ActiveSupport, que são objetos gerados à partir da classe <code>ActiveSupport::TimeWithZone</code><sup id="fnref3"><a href="#fn3">3</a></sup>. São diversos métodos e seu uso vai depender do que você precisa.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">now</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 03:37:57 PST -08:00</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">today</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">current</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 03:38:17 PST -08:00</span>

<span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">.</span><span class="nf">ago</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 02:38:28 PST -08:00</span>

<span class="mi">1</span><span class="p">.</span><span class="nf">day</span><span class="p">.</span><span class="nf">from_now</span>
<span class="c1">#=&gt; Wed, 09 Dec 2015 03:38:36 PST -08:00</span>

<span class="no">Date</span><span class="p">.</span><span class="nf">yesterday</span>
<span class="c1">#=&gt; Mon, 07 Dec 2015</span>

<span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span>
<span class="c1">#=&gt; Wed, 09 Dec 2015</span>
</code></pre></div>
<p>De um modo geral:</p>

<ul>
<li>Use <code>Time.current</code> em vez de <code>Time.now</code>.</li>
<li>Use <code>Date.current</code> em vez de <code>Date.today</code>.</li>
</ul>

<p>É importante dizer que <code>Time.current</code> pode ignorar informações de fuso horário caso esta configuração não esteja definida; o mesmo é válido para <code>Date.current</code>. Veja a implementação destes métodos na biblioteca ActiveSupport:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># activesupport-4.2.5/lib/active_support/core_ext/time/calculations.rb</span>
<span class="c1"># line 30</span>
<span class="k">class</span> <span class="nc">Time</span>
  <span class="k">def</span> <span class="nf">current</span>
    <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="p">?</span> <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">now</span> <span class="p">:</span> <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="c1"># activesupport-4.2.5/lib/active_support/core_ext/date/calculations.rb</span>
<span class="c1"># line 46</span>
<span class="k">class</span> <span class="nc">Date</span>
  <span class="k">def</span> <span class="nf">current</span>
    <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="p">?</span> <span class="o">::</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">today</span> <span class="p">:</span> <span class="o">::</span><span class="no">Date</span><span class="p">.</span><span class="nf">today</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>Você pode usar <code>Time.zone.today</code> e <code>Time.zone.now</code> para ter um resultado determinístico.</p>

<h3>Lidando com o horário de verão</h3>

<p>O ActiveSupport utiliza a gem <a href="https://rubygems.org/gems/tzinfo">TZInfo</a> para ter conhecimento sobre os diversos fusos horários. Para isso, essa gem carrega diversas informações do sistema operacional; no Ubuntu, essas informações são disponibilizadas através do pacote <code>tzdata</code>.</p>

<p>Se você mantém o seu servidor atualizado, isso significa que a aplicação terá acesso às informações sobre quando começa ou termina o <a href="https://pt.wikipedia.org/wiki/Hor%C3%A1rio_de_ver%C3%A3o">Horário de Verão</a> por todo o mundo.</p>

<p>Veja, por exemplo, como fica o Horário de Verão deste ano<sup id="fnref4"><a href="#fn4">4</a></sup>:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'2015-10-17'</span><span class="p">).</span><span class="nf">dst?</span>
<span class="c1">#=&gt; false</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'2015-10-18'</span><span class="p">).</span><span class="nf">dst?</span>
<span class="c1">#=&gt; true</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'2016-02-20'</span><span class="p">).</span><span class="nf">dst?</span>
<span class="c1">#=&gt; true</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'2016-02-21'</span><span class="p">).</span><span class="nf">dst?</span>
<span class="c1">#=&gt; false</span>
</code></pre></div>
<p>Às vezes você precisará fazer a conversão e o horário de verão pode retornar um horário diferente. Recentemente precisei integrar a geração de datas com um calendário. A maior dificuldade foi em relação à exibição de uma data futura, levando em consideração o fuso horário local. </p>

<p>Acontece que a solução é bastante simples; basta não definir o fuso horário na string que será interpretada, deixando isso para o método <code>Time.use_zone</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">current</span>
<span class="c1">#=&gt; Mon, 07 Dec 2015 18:57:51 BRST -02:00</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">use_zone</span><span class="p">(</span><span class="s2">"America/Sao_Paulo"</span><span class="p">)</span> <span class="k">do</span>
  <span class="n">starts_at</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2016-03-05 10:00"</span><span class="p">)</span>
  <span class="c1">#=&gt; Sat, 05 Mar 2016 10:00:00 BRT -03:00</span>

  <span class="n">ends_at</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2016-03-05 17:00"</span><span class="p">)</span>
  <span class="c1">#=&gt; Sat, 05 Mar 2016 17:00:00 BRT -03:00</span>
<span class="k">end</span>
</code></pre></div>
<h3>Como as datas são persistidas</h3>

<p>O ActiveRecord é configurado por padrão para persistir as datas como <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">UTC</a>. Isso é definido pelas configurações <code>ActiveRecord::Base.default_timezone</code> e <code>ActiveRecord::Base.time_zone_aware_attributes</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">default_timezone</span>
<span class="c1">#=&gt; :utc</span>

<span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">time_zone_aware_attributes</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>Isso significa que toda vez que você define uma data para um atributo do ActiveRecord, esta data será armazenada como UTC. Ao carregar o registro, o Rails irá converter a data de volta para o fuso horário definido na aplicação<sup id="fnref5"><a href="#fn5">5</a></sup>.</p>

<p>Perceba que que a data pode ser armazenada incorretamente caso você utilize os métodos <code>Date.today</code> ou <code>Time.now</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="o">=</span> <span class="s2">"America/Los_Angeles"</span>
<span class="c1">#=&gt; "America/Los_Angeles"</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">.</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-08 02:00:00 UTC</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">current</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">.</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-08 08:00:00 UTC</span>
</code></pre></div>
<p>Essa diferença acontece porque <code>Time.now</code> ignora o fuso horário definido pela aplicação e está usando o fuso horário local do sistema (que neste caso é <code>BRST-0200</code>). Por isso é muito importante que você use uma data que tenha conhecimento do fuso horário, como mostrado anteriormente.</p>

<p>O Rails assume que todas as datas armazenadas no banco de dados estão em UTC. Se alguém adicionar um registro por fora do sistema sem considerar o fuso horário pode ter uma diferença de algumas horas na hora que esse registro for carregado na aplicação. O PostgreSQL possui um campo que tem conhecimento do fuso e que fará a conversão para você antes de persistir a informação. Infelizmente, o Rails não tem suporte nativo para <a href="http://www.postgresql.org/docs/current/static/datatype-datetime.html"><code>time with time zone</code></a>, mas você pode adicioná-lo facilmente; basta criar um arquivo de inicialização que contenha o seguinte código:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># config/initializers/active_record.rb</span>
<span class="no">ActiveRecord</span><span class="o">::</span><span class="no">ConnectionAdapters</span><span class="o">::</span><span class="no">PostgreSQLAdapter</span><span class="o">::</span>
  <span class="no">NATIVE_DATABASE_TYPES</span><span class="p">[</span><span class="ss">:datetime</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="ss">name: </span><span class="s2">"timestamp with time zone"</span><span class="p">}</span>
</code></pre></div>
<h3>Fazendo consultas de banco de dados</h3>

<p>Como o Rails considera que todas as datas são armazenas como UTC, a única coisa que você precisa garantir é que as datas estejam definidas com a informação de fuso horário (<code>Time.current</code> ou <code>Date.current</code>, por exemplo) e o framework irá se encarregar de fazer a conversão correta.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Article</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"published_at &gt;= ?"</span><span class="p">,</span> <span class="no">Time</span><span class="p">.</span><span class="nf">current</span><span class="p">)</span>
</code></pre></div>
<p>Lembre-se que definição de datas como <code>1.day.ago</code> e <code>Date.current.beginning_of_month</code>, dentre outros, irão gerar objetos que carregam o fuso horário, então você não terá problemas.</p>

<h3>Interpretando datas enviadas pelos usuários</h3>

<p>Os mesmos cuidados valem na hora de converter uma string em um objeto de data. O Ruby possui os métodos <code>Time.parse</code> e <code>Date.parse</code>, mas eles ignorarão as informações de fuso horário. Por isso, use <code>Time.zone.parse</code> sempre que precisar fazer esta conversão.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"8:47am Dec 7th, 2015"</span><span class="p">).</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-07 10:47:00 UTC</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"8:47am Dec 7th, 2015"</span><span class="p">).</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-07 16:47:00 UTC</span>
</code></pre></div>
<p>Para o caso de você receber diversos números para cada item que compõe uma data, utilize o método <code>Time.zone.local</code> em vez de <code>Time.new</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">2015</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">0</span><span class="p">).</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-07 10:47:00 UTC</span>

<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">local</span><span class="p">(</span><span class="mi">2015</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">0</span><span class="p">).</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-07 16:47:00 UTC</span>
</code></pre></div>
<p>Finalmente, se você tiver um timestamp, use <code>Time.zone.at</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">at</span><span class="p">(</span><span class="mi">1449506820</span><span class="p">)</span>
<span class="c1">#=&gt; 2015-12-07 16:47:00 UTC</span>
</code></pre></div>
<p>Para definir um fuso horário apenas durante uma execução qualquer, podemos usar o método <code>Time.use_zone</code>. Ele irá definir o fuso horário especificado apenas para a execução do bloco.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span> <span class="o">=</span> <span class="s2">"America/Sao_Paulo"</span>
<span class="c1">#=&gt; "America/Sao_Paulo"</span>

<span class="n">now_in_sao_paulo</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">current</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 09:40:31 BRST -02:00</span>

<span class="n">now_in_los_angeles</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">use_zone</span><span class="p">(</span><span class="s2">"America/Los_Angeles"</span><span class="p">)</span> <span class="k">do</span>
  <span class="no">Time</span><span class="p">.</span><span class="nf">current</span>
<span class="k">end</span>
<span class="c1">#=&gt; Tue, 08 Dec 2015 03:40:31 PST -08:00</span>

<span class="n">now_in_sao_paulo</span><span class="p">.</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-08 11:40:31 UTC</span>

<span class="n">now_in_los_angeles</span><span class="p">.</span><span class="nf">utc</span>
<span class="c1">#=&gt; 2015-12-08 11:40:31 UTC</span>

<span class="n">now_in_sao_paulo</span><span class="p">.</span><span class="nf">to_i</span> <span class="o">==</span> <span class="n">now_in_los_angeles</span><span class="p">.</span><span class="nf">to_i</span>
<span class="c1">#=&gt; true</span>
</code></pre></div>
<p>Esse método pode ser usado no controller, caso você precise definir o fuso horário especificado pelo usuário.</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">ApplicationController</span> <span class="o">&lt;</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">Base</span>
  <span class="n">around_action</span> <span class="ss">:set_timezone</span><span class="p">,</span> <span class="ss">if: :logged_in?</span>

  <span class="kp">private</span>

  <span class="k">def</span> <span class="nf">set_timezone</span><span class="p">(</span><span class="o">&amp;</span><span class="n">action</span><span class="p">)</span>
    <span class="no">Time</span><span class="p">.</span><span class="nf">use_zone</span><span class="p">(</span><span class="n">current_user</span><span class="p">.</span><span class="nf">time_zone</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">action</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<h3>Trabalhando com fuso horário no JavaScript</h3>

<p>A melhor maneira de se trabalhar com datas e fuso horário no JavaScript é usar a biblioteca <a href="http://momentjs.com">moment.js</a>.</p>

<p>Para enviar uma data do Ruby para o JavaScript, utilize o método <code>Time#iso8601</code>. Ele irá retornar uma formatação como <code>2015-12-07T19:25:45Z</code>. Com o moment.js, você pode fazer a conversão desse formato para o objeto <code>Date</code> do JavaScript.</p>
<div class="highlight"><pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">date</span> <span class="o">=</span> <span class="nf">moment</span><span class="p">(</span><span class="dl">"</span><span class="s2">2015-12-07T19:25:45Z</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">//=&gt; Mon Dec 07 2015 17:25:45 GMT-0200 (BRST)</span>

<span class="nx">date</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span><span class="dl">"</span><span class="s2">MMM DD, YYYY - h:mma</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">//=&gt; Dec 07, 2015 - 5:25pm</span>
</code></pre></div>
<p>No exemplo acima ele fez a conversão automática para o fuso horário identificado pelo navegador, no meu caso <code>BRST-0200</code>. Se você precisar fazer conversão para outro fuso horário, use o <a href="http://momentjs.com/timezone">momentjs-timezone</a>.</p>

<h2>Finalizando</h2>

<p>Se o seu aplicativo usa datas, principalmente quando há cálculo envolvido, entender como funciona o sistema de datas do Rails é essencial. Para facilitar a sua vida, basta seguir as regras abaixo:</p>

<ul>
<li>Defina o fuso horário de seus servidores como <code>Etc/UTC</code>.</li>
<li>Defina sempre o fuso horário em sua aplicação Ruby on Rails, mesmo que seja <code>Etc/UTC</code>.</li>
<li>Lide com a apresentação do fuso horário na camada da aplicação.</li>
<li>Use os métodos <code>Time.current</code> e <code>Date.current</code> para pegar as datas atuais.</li>
<li>Use <code>Time.zone.parse</code> para converter strings em datas.</li>
<li>Converta <code>Date</code> em <code>ActiveSupport::TimeWithZone</code> para fazer comparações, como em <code>date.in_time_zone == Time.current.beginning_of_day</code>.</li>
</ul>

<h2>Resources</h2>

<ul>
<li><a href="http://brendankemp.com/essays/dealing-with-time-zones-using-rails-and-postgres/">Dealing With Time Zones Using Rails and Postgres</a></li>
<li><a href="https://www.reinteractive.net/posts/168-dealing-with-timezones-effectively-in-rails">Dealing with timezones effectively in Rails</a></li>
<li><a href="http://alwayscoding.ca/momentos/2013/08/22/handling-dates-and-timezones-in-ruby-and-rails/">Handling Dates &amp; Timezones in Ruby &amp; Rails</a></li>
<li><a href="http://brendankemp.com/essays/handling-time-zones-in-rails/">Handling Time Zones in Rails</a></li>
<li><a href="http://makandracards.com/makandra/646-how-rails-and-mysql-are-handling-time-zones">How Rails and MySQL are handling time zones</a></li>
<li><a href="http://danilenko.org/2012/7/6/rails_timezones/">The Exhaustive Guide to Rails Time Zones</a></li>
<li><a href="http://yellerapp.com/posts/2015-01-12-the-worst-server-setup-you-can-make.html">The Worst Server Setup Mistake You Can Make</a></li>
<li><a href="http://www.elabs.se/blog/36-working-with-time-zones-in-ruby-on-rails">Working with time zones in Ruby on Rails</a></li>
</ul>

<div class="footnotes">
<hr>
<ol>

<li id="fn1">
<p>Existe uma terceira classe chamada <code>DateTime</code>, que é apenas uma subclasse de <code>Date</code> com conhecimento sobre os atributos de hora. Em versões antigas do Ruby, a classe <code>Time</code> tinha limitações no intervalo de datas em sistemas 32-bit; nestas situações, o uso de <code>DateTime</code> era recomendado. À partir do Ruby 1.9.2 essa limitação foi removida e a classe <code>Time</code> pode agora trabalhar com qualquer intervalo de datas. Além disso, as classes <code>Time</code> e <code>Date</code> tem <a href="https://gist.github.com/fnando/54ddc21c4640d09c30ce">performance semelhante</a>.&nbsp;<a href="#fnref1">&#8617;</a></p>
</li>

<li id="fn2">
<p>O PostgreSQL só irá usar a variável de ambiente <code>TZ</code> caso a configuração <code>timezone</code> não tenha sido especificada no arquivo <code>postgresql.conf</code>. No entanto, como ela é definida com um valor padrão, é muito improvável que o PostgreSQL use a variável <code>TZ</code> a não ser que você tenha essa intenção.&nbsp;<a href="#fnref2">&#8617;</a></p>
</li>

<li id="fn3">
<p>Os métodos que retornam datas, como <code>Time.zone.today</code>, <code>Date.yesterday</code> e <code>Date.tomorrow</code> retornam objetos da classe <code>Date</code> e, por este motivo, não possuem conhecimento sobre fuso horário. Se precisar, converta estes objetos para um objeto do tipo <code>ActiveSupport::TimeWithZone</code> com <code>date.in_time_zone</code>.&nbsp;<a href="#fnref3">&#8617;</a></p>
</li>

<li id="fn4">
<p>O <a href="https://pt.wikipedia.org/wiki/Lista_de_per%C3%ADodos_em_que_vigorou_o_hor%C3%A1rio_de_ver%C3%A3o_no_Brasil">horário de verão 2016</a> vai de 18 de outubro de 2015 a 21 de fevereiro de 2016.&nbsp;<a href="#fnref4">&#8617;</a></p>
</li>

<li id="fn5">
<p>A configuracão <code>ActiveRecord::Base.time_zone_aware_attributes</code> é desativada por padrão se você está usando o ActiveRecord fora do Ruby on Rails.&nbsp;<a href="#fnref5">&#8617;</a></p>
</li>

</ol>
</div>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/trabalhando-com-datas-no-ruby-on-rails</link>
      <guid>https://nandovieira.com.br/trabalhando-com-datas-no-ruby-on-rails</guid>
      <pubDate>Tue, 08 Dec 2015 10:40:00 -0200</pubDate>
    </item>
    <item>
      <title>Sobre os apps que uso no Mac</title>
      <description>
        <![CDATA[<p>Sempre que <a href="http://howtocode.com.br">estou dando aulas</a> ou mostro algo no meu Macbook para alguma pessoa, me perguntam sobre os apps que uso. Tem muitos utilitários que gosto e que aparentemente muita gente não conhece. Neste artigo mostro alguns dos apps que uso e que valem a menção.</p>

<h2>Bartender</h2>

<p>O <a href="https://www.macbartender.com">Bartender</a> ($15) é um daqueles aplicativos essenciais que uso para diminuir as distrações da tela. Veja, por exemplo, como é a menu bar usual de um Mac.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/icons-without-bartender.jpg" alt="Menubar do Mac OS X"></p>

<p>Note como são exibidos mais de 20 ícones. Isso é ruim principalmente se você faz screencasts, pois gera um ruído completamente desnecessário. Em contra-partida, com o Bartender é possível controlar quais ícones serão exibidos. Na minha atual configuração, exibo apenas 6 ícones, como você pode ver abaixo. Todos os outros ícones podem ser visualizados com um simples clique.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/bartender.gif" alt="Bartender em ação"></p>

<h2>Backdrop</h2>

<p>Já que falamos em screencasts, o <a href="https://itunes.apple.com/us/app/backdrop/id411461952?mt=12">Backdrop</a> (grátis) é um app que permite esconder os ícones de sua área de trabalho, útil para casos onde você não tem esse suporte nativamente nos apps de screencasting, ou se vai gravar GIFs animados.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/backdrop.gif" alt="Backdrop"></p>

<p>O Backdrop permite que você configure uma cor ou imagem de fundo, facilitando a configuração de seu workspace de gravação.</p>

<h2>ScreenFlow</h2>

<p>O <a href="http://www.telestream.net/screenflow/overview.htm">ScreenFlow</a> ($99) é um dos apps mais poderosos para gravação de screencasting. Ele permite gravar tudo em separado (câmera, áudio, tela, cliques e atalhos de teclado), de modo que você tenha controle total na hora de editar o seu vídeo. Uma das maiores vantagens é que ele é muito simples de usar.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/screenflow.png" alt="Screenflow 5"></p>

<h2>Screenflick</h2>

<p>Além do ScreenFlow, uso bastante o <a href="http://www.araelium.com/screenflick">Screenflick</a> ($29), principalmente para gravações de longa duração, como as dos <a href="http://howtocode.com.br">meus workshops</a>. A maior vantagem é que ele tem uma performance melhor, sem deixar de ter funcionalidades interessantes como gravação da câmera, atalhos de teclado e cliques de mouse. Ele não possui funcionalidades avançadas de edição e só permite fazer <em>trimming</em>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/screenflick.png" alt="Screenflick 2"></p>

<h2>Dash</h2>

<p>O <a href="https://kapeli.com/dash">Dash</a> ($25) é um daqueles apps que você não dá muita bola no começo mas que depois se mostra essencial. Com ele você instalar documentação para diversas linguagens e frameworks, permitindo que você faça buscas em um único lugar e sem precisar de uma conexão com a Internet. Pode ser integrado com a maioria dos editores e com o Alfred.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/dash.png" alt="Pesquisando documentação com Dash"></p>

<p>Existe uma <a href="https://zealdocs.org">alternativa opensource</a> que não tem um acabamento tão bom, mas que usa os mesmos pacotes de documentação do Dash.</p>

<h2>Alfred</h2>

<p>Um dos primeiros apps que conheci quando comecei a usar Mac foi o <a href="https://qsapp.com">Quicksilver</a>. Como ele não era muito atualizado, não demorou muito para surgir uma alternativa. Trata-se do <a href="https://www.alfredapp.com">Alfred</a> (£17), essencial no meu dia-a-dia. Com uma interface muito bem feita, é flexível e permite que você crie <em>workflows</em> (extensões que automatizam um processo) de um modo bastante simples.</p>

<video controls loop width="836">
  <source src="https://s3.amazonaws.com/nandovieira/media/alfred.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/alfred.webm" type="video/webm"></source>
</video>

<p>O Alfred não é apenas um lançador de aplicativos. Ele permite que você configure os mais diversos workflows. Veja, por exemplo, como fica a integração com o Dash.</p>

<video controls loop width="836">
  <source src="https://s3.amazonaws.com/nandovieira/media/alfred-dash.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/alfred-dash.webm" type="video/webm"></source>
</video>

<p>O Alfred tem uma versão gratuita que não faz muito além de abrir aplicativos.</p>

<h2>ImageOptim</h2>

<p>Embora você possa automatizar o processo de comprimir imagens sem perder a qualidade, às vezes é mais fácil apenas arrastar as imagens para um app. Neste caso, <a href="https://imageoptim.com">ImageOptim</a> (grátis) é o aplicativo indicado para você.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/imageoptim.png" alt="ImageOptim"></p>

<h2>Annotate</h2>

<p>O <a href="https://www.driftt.com/annotate-mac">Annotate</a> ($2) permite fazer anotações em imagens existentes e também em screenshots. Ele possui integração com o Dropbox e permite gravar GIFs de até 10 segundos.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/annotate.png" alt="Annotate"></p>

<h2>IconPing</h2>

<p>Já pensou em dar 20 minutos de aula para só então perceber que sua conexão tinha caído e você não tinha a menor ideia? Pois é, já aconteceu comigo. Com o <a href="https://github.com/fnando/iconping">IconPing</a> (grátis) você terá um indicativo de como está sua conexão.</p>

<video controls loop width="342">
  <source src="https://s3.amazonaws.com/nandovieira/media/iconping.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/iconping.webm" type="video/webm"></source>
</video>

<h2>LICEcap</h2>

<p>Com o <a href="http://www.cockos.com/licecap/">LICEcap</a> (grátis) você pode criar GIFs animados sem limite de tempo com uma qualidade muito boa. É possível configurar as dimensões da gravação, além de configurar a quantidade de quadros por segundo.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/licecap.png" alt="LICEcap"></p>

<h2>Servus</h2>

<p>Servus é uma alternativa para o <a href="https://www.getcloudapp.com">CloudApp</a> que integra com o Dropbox. Infelizmente ele foi descontinuado, mas é listado neste artigo porque continua funcionando muito bem, sem nenhum erro. Se você conhece alguma alternativa que integra com o Dropbox, deixe seu comentário.</p>

<video controls loop width="684">
  <source src="https://s3.amazonaws.com/nandovieira/media/servus.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/servus.webm" type="video/webm"></source>
</video>

<h2>DaisyDisk</h2>

<p><a href="https://daisydiskapp.com">DaisyDisk</a> ($10) é um daqueles apps que uso frequentemente. Embora existam alternativas opensource, o que gosto neste app é que sua interface é muito bem trabalhada. Você consegue visualizar facilmente como anda o uso de seus discos (incluindo externos), permitindo que você apague rapidamente o que não é mais útil.</p>

<video controls loop width="828">
  <source src="https://s3.amazonaws.com/nandovieira/media/daisydisk.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/daisydisk.webm" type="video/webm"></source>
</video>

<h2>MindNode</h2>

<p>Ultimamente tenho usado <em>mindmaps</em> para ter uma ideia geral de assuntos que quero abordar em palestras e artigos. Não apenas para isso, mas para tudo que possa se beneficiar de <em>brainstorming</em>. Com o <a href="https://mindnode.com">MindNode</a> ($30) você pode criar esses mindmaps em uma interface muito fácil de usar e exportar para diversos formatos, incluindo PDF e imagens.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/mindnode.png" alt="MindNode"></p>

<h2>Runway</h2>

<p>O <a href="http://celestialteapot.com/runway/">Runway</a> ($10) é uma ferramenta que permite criar diagramas de diversos tipos. Comprei esse app recentemente para fazer alguns diagramas de banco de dados<sup id="fnref1"><a href="#fn1">1</a></sup> e vem me servindo muito bem para este propósito.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/runway-diagram.png" alt="Runway"></p>

<h2>Boom</h2>

<p>Sabe quando você está em algum lugar, sem seu fone de ouvido, e queria poder aumentar um pouco mais o som de seu computador? Com o <a href="http://www.globaldelight.com/boom/index.php">Boom</a> ($15) você pode fazer isso.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/boom2.jpg" alt="Boom"></p>

<h2>Reflector</h2>

<p>Quando dou workshops de Responsive Web Design, é interessante demonstrar em smartphones e tablets reais como está ficando o markup que está sendo implementado. Nestes casos, gosto de usar o <a href="http://www.airsquirrels.com/reflector/">Reflector</a> ($15), que recebe conexões de dispositivos baseados em iOS e Android, exibindo na tela tudo o que você está fazendo no dispositivo. Também é muito útil se você precisa gravar vídeos da interação do usuário com sites e aplicativos nestes dispositivos.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/reflector2.jpg" alt="Reflector"></p>

<h2>Should I Sleep</h2>

<p>Você provavelmente já conhece o <a href="http://lightheadsw.com/caffeine/">Caffeine</a> (grátis). Ele evita que seu computador entre em modo de hibernação ou ative o protetor de telas. Com o <a href="https://itunes.apple.com/us/app/should-i-sleep/id560851219?mt=12">Should I Sleep</a> ($2) você leva esse conceito a outro nível!</p>

<p>O Should I Sleep tem diversos tipos de detecção, como facial, movimentos e som, dentre outros, com a possibilidade de configurar o nível de sensibilidade da detecção.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/should-i-sleep.jpg" alt="Should I Sleep"></p>

<p>Infelizmente o app parece estar abandonado, pois há tempos não recebe atualização (não que isso seja um problema, já que o app funciona muito bem).</p>

<h2>Sip</h2>

<p><a href="http://theolabrothers.com/pro.html">Sip</a> ($10) é fundamentalmente um <em>color picker</em>. Mas além disso, ele permite que você salve temas de cores que você coletou, sincronizando entre computadores diferentes. Você pode escolher diferentes formatos de cor para copiar, como hexadecimal (CSS) ou formatos específicos de linguagens de programação.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/sip.jpg" alt="Sip"></p>

<h2>Sketch</h2>

<p>Há pouco mais de um ano, deixei de usar o Photoshop para criação de designs para web. Agora uso o <a href="http://www.sketchapp.com">Sketch</a> ($99), que é programa vetorial que tem uma série de funcionalidades interessantes, como a possibilidade de exportar assets em diversos formatos e escalas de uma só vez (como SVG, imagens em 1x e 2x).</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/sketch.jpg" alt="Sketch"></p>

<h2>1Password</h2>

<p>O <a href="http://1password.com">1Password</a> ($50 para Mac, $10 para iOS) é definitivamente o melhor gerenciador de senhas que existe. Com ele você consegue manter em um só lugar e de forma sincronizada todas as suas senhas (geradas randomicamente), cartões de créditos e outras informações importantes, como números seriais de programas. Ele possui extensões para todos os navegadores, facilitando ainda mais seu uso no dia-a-dia.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/1password.jpg" alt="1Password"></p>

<p>Uma das melhores coisas do 1Password é que ele também serve para gerar tokens TOTP (autenticação de dois fatores) tanto no Macbook, quanto no iPhone.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/1password-totp.jpg" alt="Suporte para TOTP no 1Password"></p>

<h2>Finalizando</h2>

<p>Como você pode ver, o ecossistema de aplicativos do Mac OS X é imbatível. Não é apenas sobre a quantidade de ofertas disponíveis e que cobrem todas as necessidades que tenho, mas é principalmente sobre a qualidade dos aplicativos.</p>

<p>Em nenhum outro sistema operacional, seja Windows ou Linux<sup id="fnref2"><a href="#fn2">2</a></sup>, você conseguirá encontrar aplicativos tão bem feitos quanto os de Mac. Claro que isso vem com um custo, mas estou disposto a pagar pelo que há de melhor em nível de software.</p>

<div class="footnotes">
<hr>
<ol>

<li id="fn1">
<p>Meu desejo era comprar o <a href="http://www.navicat.com/products/navicat-data-modeler">Navicat Data Modeler</a>, mas o seu valor de $250 é completamente fora da realidade. E usar o MySQL Workbench não é uma opção.&nbsp;<a href="#fnref1">&#8617;</a></p>
</li>

<li id="fn2">
<p>Ouvi dizer que 2016 será o ano do Linux no desktop, assim como foi todo ano nos últimos 10 anos.&nbsp;<a href="#fnref2">&#8617;</a></p>
</li>

</ol>
</div>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p>Sempre que <a href="http://howtocode.com.br">estou dando aulas</a> ou mostro algo no meu Macbook para alguma pessoa, me perguntam sobre os apps que uso. Tem muitos utilitários que gosto e que aparentemente muita gente não conhece. Neste artigo mostro alguns dos apps que uso e que valem a menção.</p>

<h2>Bartender</h2>

<p>O <a href="https://www.macbartender.com">Bartender</a> ($15) é um daqueles aplicativos essenciais que uso para diminuir as distrações da tela. Veja, por exemplo, como é a menu bar usual de um Mac.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/icons-without-bartender.jpg" alt="Menubar do Mac OS X"></p>

<p>Note como são exibidos mais de 20 ícones. Isso é ruim principalmente se você faz screencasts, pois gera um ruído completamente desnecessário. Em contra-partida, com o Bartender é possível controlar quais ícones serão exibidos. Na minha atual configuração, exibo apenas 6 ícones, como você pode ver abaixo. Todos os outros ícones podem ser visualizados com um simples clique.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/bartender.gif" alt="Bartender em ação"></p>

<h2>Backdrop</h2>

<p>Já que falamos em screencasts, o <a href="https://itunes.apple.com/us/app/backdrop/id411461952?mt=12">Backdrop</a> (grátis) é um app que permite esconder os ícones de sua área de trabalho, útil para casos onde você não tem esse suporte nativamente nos apps de screencasting, ou se vai gravar GIFs animados.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/backdrop.gif" alt="Backdrop"></p>

<p>O Backdrop permite que você configure uma cor ou imagem de fundo, facilitando a configuração de seu workspace de gravação.</p>

<h2>ScreenFlow</h2>

<p>O <a href="http://www.telestream.net/screenflow/overview.htm">ScreenFlow</a> ($99) é um dos apps mais poderosos para gravação de screencasting. Ele permite gravar tudo em separado (câmera, áudio, tela, cliques e atalhos de teclado), de modo que você tenha controle total na hora de editar o seu vídeo. Uma das maiores vantagens é que ele é muito simples de usar.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/screenflow.png" alt="Screenflow 5"></p>

<h2>Screenflick</h2>

<p>Além do ScreenFlow, uso bastante o <a href="http://www.araelium.com/screenflick">Screenflick</a> ($29), principalmente para gravações de longa duração, como as dos <a href="http://howtocode.com.br">meus workshops</a>. A maior vantagem é que ele tem uma performance melhor, sem deixar de ter funcionalidades interessantes como gravação da câmera, atalhos de teclado e cliques de mouse. Ele não possui funcionalidades avançadas de edição e só permite fazer <em>trimming</em>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/screenflick.png" alt="Screenflick 2"></p>

<h2>Dash</h2>

<p>O <a href="https://kapeli.com/dash">Dash</a> ($25) é um daqueles apps que você não dá muita bola no começo mas que depois se mostra essencial. Com ele você instalar documentação para diversas linguagens e frameworks, permitindo que você faça buscas em um único lugar e sem precisar de uma conexão com a Internet. Pode ser integrado com a maioria dos editores e com o Alfred.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/dash.png" alt="Pesquisando documentação com Dash"></p>

<p>Existe uma <a href="https://zealdocs.org">alternativa opensource</a> que não tem um acabamento tão bom, mas que usa os mesmos pacotes de documentação do Dash.</p>

<h2>Alfred</h2>

<p>Um dos primeiros apps que conheci quando comecei a usar Mac foi o <a href="https://qsapp.com">Quicksilver</a>. Como ele não era muito atualizado, não demorou muito para surgir uma alternativa. Trata-se do <a href="https://www.alfredapp.com">Alfred</a> (£17), essencial no meu dia-a-dia. Com uma interface muito bem feita, é flexível e permite que você crie <em>workflows</em> (extensões que automatizam um processo) de um modo bastante simples.</p>

<video controls loop width="836">
  <source src="https://s3.amazonaws.com/nandovieira/media/alfred.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/alfred.webm" type="video/webm"></source>
</video>

<p>O Alfred não é apenas um lançador de aplicativos. Ele permite que você configure os mais diversos workflows. Veja, por exemplo, como fica a integração com o Dash.</p>

<video controls loop width="836">
  <source src="https://s3.amazonaws.com/nandovieira/media/alfred-dash.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/alfred-dash.webm" type="video/webm"></source>
</video>

<p>O Alfred tem uma versão gratuita que não faz muito além de abrir aplicativos.</p>

<h2>ImageOptim</h2>

<p>Embora você possa automatizar o processo de comprimir imagens sem perder a qualidade, às vezes é mais fácil apenas arrastar as imagens para um app. Neste caso, <a href="https://imageoptim.com">ImageOptim</a> (grátis) é o aplicativo indicado para você.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/imageoptim.png" alt="ImageOptim"></p>

<h2>Annotate</h2>

<p>O <a href="https://www.driftt.com/annotate-mac">Annotate</a> ($2) permite fazer anotações em imagens existentes e também em screenshots. Ele possui integração com o Dropbox e permite gravar GIFs de até 10 segundos.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/annotate.png" alt="Annotate"></p>

<h2>IconPing</h2>

<p>Já pensou em dar 20 minutos de aula para só então perceber que sua conexão tinha caído e você não tinha a menor ideia? Pois é, já aconteceu comigo. Com o <a href="https://github.com/fnando/iconping">IconPing</a> (grátis) você terá um indicativo de como está sua conexão.</p>

<video controls loop width="342">
  <source src="https://s3.amazonaws.com/nandovieira/media/iconping.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/iconping.webm" type="video/webm"></source>
</video>

<h2>LICEcap</h2>

<p>Com o <a href="http://www.cockos.com/licecap/">LICEcap</a> (grátis) você pode criar GIFs animados sem limite de tempo com uma qualidade muito boa. É possível configurar as dimensões da gravação, além de configurar a quantidade de quadros por segundo.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/licecap.png" alt="LICEcap"></p>

<h2>Servus</h2>

<p>Servus é uma alternativa para o <a href="https://www.getcloudapp.com">CloudApp</a> que integra com o Dropbox. Infelizmente ele foi descontinuado, mas é listado neste artigo porque continua funcionando muito bem, sem nenhum erro. Se você conhece alguma alternativa que integra com o Dropbox, deixe seu comentário.</p>

<video controls loop width="684">
  <source src="https://s3.amazonaws.com/nandovieira/media/servus.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/servus.webm" type="video/webm"></source>
</video>

<h2>DaisyDisk</h2>

<p><a href="https://daisydiskapp.com">DaisyDisk</a> ($10) é um daqueles apps que uso frequentemente. Embora existam alternativas opensource, o que gosto neste app é que sua interface é muito bem trabalhada. Você consegue visualizar facilmente como anda o uso de seus discos (incluindo externos), permitindo que você apague rapidamente o que não é mais útil.</p>

<video controls loop width="828">
  <source src="https://s3.amazonaws.com/nandovieira/media/daisydisk.mp4" type="video/mp4"></source>
  <source src="https://s3.amazonaws.com/nandovieira/media/daisydisk.webm" type="video/webm"></source>
</video>

<h2>MindNode</h2>

<p>Ultimamente tenho usado <em>mindmaps</em> para ter uma ideia geral de assuntos que quero abordar em palestras e artigos. Não apenas para isso, mas para tudo que possa se beneficiar de <em>brainstorming</em>. Com o <a href="https://mindnode.com">MindNode</a> ($30) você pode criar esses mindmaps em uma interface muito fácil de usar e exportar para diversos formatos, incluindo PDF e imagens.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/mindnode.png" alt="MindNode"></p>

<h2>Runway</h2>

<p>O <a href="http://celestialteapot.com/runway/">Runway</a> ($10) é uma ferramenta que permite criar diagramas de diversos tipos. Comprei esse app recentemente para fazer alguns diagramas de banco de dados<sup id="fnref1"><a href="#fn1">1</a></sup> e vem me servindo muito bem para este propósito.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/runway-diagram.png" alt="Runway"></p>

<h2>Boom</h2>

<p>Sabe quando você está em algum lugar, sem seu fone de ouvido, e queria poder aumentar um pouco mais o som de seu computador? Com o <a href="http://www.globaldelight.com/boom/index.php">Boom</a> ($15) você pode fazer isso.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/boom2.jpg" alt="Boom"></p>

<h2>Reflector</h2>

<p>Quando dou workshops de Responsive Web Design, é interessante demonstrar em smartphones e tablets reais como está ficando o markup que está sendo implementado. Nestes casos, gosto de usar o <a href="http://www.airsquirrels.com/reflector/">Reflector</a> ($15), que recebe conexões de dispositivos baseados em iOS e Android, exibindo na tela tudo o que você está fazendo no dispositivo. Também é muito útil se você precisa gravar vídeos da interação do usuário com sites e aplicativos nestes dispositivos.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/reflector2.jpg" alt="Reflector"></p>

<h2>Should I Sleep</h2>

<p>Você provavelmente já conhece o <a href="http://lightheadsw.com/caffeine/">Caffeine</a> (grátis). Ele evita que seu computador entre em modo de hibernação ou ative o protetor de telas. Com o <a href="https://itunes.apple.com/us/app/should-i-sleep/id560851219?mt=12">Should I Sleep</a> ($2) você leva esse conceito a outro nível!</p>

<p>O Should I Sleep tem diversos tipos de detecção, como facial, movimentos e som, dentre outros, com a possibilidade de configurar o nível de sensibilidade da detecção.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/should-i-sleep.jpg" alt="Should I Sleep"></p>

<p>Infelizmente o app parece estar abandonado, pois há tempos não recebe atualização (não que isso seja um problema, já que o app funciona muito bem).</p>

<h2>Sip</h2>

<p><a href="http://theolabrothers.com/pro.html">Sip</a> ($10) é fundamentalmente um <em>color picker</em>. Mas além disso, ele permite que você salve temas de cores que você coletou, sincronizando entre computadores diferentes. Você pode escolher diferentes formatos de cor para copiar, como hexadecimal (CSS) ou formatos específicos de linguagens de programação.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/sip.jpg" alt="Sip"></p>

<h2>Sketch</h2>

<p>Há pouco mais de um ano, deixei de usar o Photoshop para criação de designs para web. Agora uso o <a href="http://www.sketchapp.com">Sketch</a> ($99), que é programa vetorial que tem uma série de funcionalidades interessantes, como a possibilidade de exportar assets em diversos formatos e escalas de uma só vez (como SVG, imagens em 1x e 2x).</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/sketch.jpg" alt="Sketch"></p>

<h2>1Password</h2>

<p>O <a href="http://1password.com">1Password</a> ($50 para Mac, $10 para iOS) é definitivamente o melhor gerenciador de senhas que existe. Com ele você consegue manter em um só lugar e de forma sincronizada todas as suas senhas (geradas randomicamente), cartões de créditos e outras informações importantes, como números seriais de programas. Ele possui extensões para todos os navegadores, facilitando ainda mais seu uso no dia-a-dia.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/1password.jpg" alt="1Password"></p>

<p>Uma das melhores coisas do 1Password é que ele também serve para gerar tokens TOTP (autenticação de dois fatores) tanto no Macbook, quanto no iPhone.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/1password-totp.jpg" alt="Suporte para TOTP no 1Password"></p>

<h2>Finalizando</h2>

<p>Como você pode ver, o ecossistema de aplicativos do Mac OS X é imbatível. Não é apenas sobre a quantidade de ofertas disponíveis e que cobrem todas as necessidades que tenho, mas é principalmente sobre a qualidade dos aplicativos.</p>

<p>Em nenhum outro sistema operacional, seja Windows ou Linux<sup id="fnref2"><a href="#fn2">2</a></sup>, você conseguirá encontrar aplicativos tão bem feitos quanto os de Mac. Claro que isso vem com um custo, mas estou disposto a pagar pelo que há de melhor em nível de software.</p>

<div class="footnotes">
<hr>
<ol>

<li id="fn1">
<p>Meu desejo era comprar o <a href="http://www.navicat.com/products/navicat-data-modeler">Navicat Data Modeler</a>, mas o seu valor de $250 é completamente fora da realidade. E usar o MySQL Workbench não é uma opção.&nbsp;<a href="#fnref1">&#8617;</a></p>
</li>

<li id="fn2">
<p>Ouvi dizer que 2016 será o ano do Linux no desktop, assim como foi todo ano nos últimos 10 anos.&nbsp;<a href="#fnref2">&#8617;</a></p>
</li>

</ol>
</div>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/sobre-os-apps-que-uso-no-mac</link>
      <guid>https://nandovieira.com.br/sobre-os-apps-que-uso-no-mac</guid>
      <pubDate>Fri, 04 Dec 2015 19:57:00 -0200</pubDate>
    </item>
    <item>
      <title>Dicas para quem quer começar a palestrar</title>
      <description>
        <![CDATA[<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/eu-palestrando.jpg" alt="Eu palestrando no 16º Encontro Locaweb" class="transparent"></p>

<p>Muitas pessoas me perguntam como se tornar um palestrante ou como fazer slides melhores com uma certa frequência. Eu normalmente dou algumas dicas, mas não dou detalhes, pois é um assunto complexo.</p>

<p>Então, desta vez decidi documentar neste artigo uma série de dicas que podem auxiliar você nessa difícil tarefa que é entregar conteúdo útil em uma palestra com tempo limitado.</p>

<h2>Escolhendo o tema</h2>

<p>Escolher o tema do que você quer palestrar é uma das tarefas mais difíceis. O maior problema é que <em>achamos que não temos nada interessante para falar</em>. E isso simplesmente não é verdade!</p>

<p>Sempre existirão pessoas mais experientes que você em eventos, assim como também sempre existirão pessoas que estão começando e que são menos experientes. Por isso, mesmo que você julgue seu tema como sendo básico, ele pode ser avançado e interessante o bastante para estas pessoas.</p>

<p>Além disso, mesmo que seja um assunto que já foi exaurido, você pode dar uma nova perspectiva sobre o assunto. Basta não falar o que já foi dito extensivamente por outras pessoas.</p>

<p>Um problema muito comum é sobre o escopo de sua palestra. Tenha em mente que o tempo reduzido tornará difícil a tarefa de abordar um escopo muito grande. Por isso, experimente focar em uma parte do conteúdo em vez de tentar abraçar o mundo.</p>

<h3>Falando sobre algo que você domina</h3>

<p>Se você está dando sua primeira palestra, escolher um assunto que você domina é uma excelente escolha. Isso porque você poderá se dedicar em aprender como palestrar melhor, em vez de também ter que aprender sobre o assunto da palestra.</p>

<p>Estas palestras podem beneficiar pessoas experientes e inexperientes. Para pessoas experientes, você pode falar sobre dicas e assuntos mais avançados. No caso de pessoas inexperientes, você pode dar dicas que como ter um início mais sólido, tirando da frente problemas que você mesmo já enfrentou.</p>

<h3>Falando sobre algo que você tem interesse</h3>

<p>Às vezes você decide por falar sobre algo que você tem interesse. Dar uma palestra sobre este assunto é uma excelente maneira de se aprofundar e aprender mais sobre ele. Penso que é melhor fazer isso quando você já tem uma certa experiência em dar palestras, já que você precisará se preocupar em aprender mais sobre o que vai falar.</p>

<p>Normalmente são palestras apaixonadas e que podem estimular os participantes por causa de sua empolgação sobre aquele assunto.</p>

<h3>Falando sobre algo que é popular</h3>

<p>Uma excelente maneira de garantir que sua palestra seja aceita em eventos é falar sobre assuntos populares do momento. Neste caso, você pode abordar o assunto sobre a ótica de porque ele é mais relevante que uma tecnologia concorrente ou porque alguém deveria considerá-lo para começo de conversa.</p>

<h3>Falando sobre algo que é importante</h3>

<p>Muitos assuntos são de suma importância e também tem grandes chances de fazerem sucesso em conferências. Eu, por exemplo, gosto muito de falar sobre segurança em aplicações web, pois mesmo sendo um assunto essencial e muito apresentado, ainda não é algo que todos dominam (e que muitos desconhecem totalmente).</p>

<p>Quando apresentadas com um tom de urgência, podem estimular os participantes a procurarem mais sobre o assunto após a palestra.</p>

<h2>Preparando sua palestra</h2>

<p>Depois de escolher o tema, é hora de preparar sua palestra. Ultimamente tenho usado <a href="https://mindnode.com">mindmaps</a> para organizar as minhas ideias. Veja, por exemplo, como ficou o mindmap da minha palestra da Rubyconf Brasil 2015.</p>

<p><a href="https://s3.amazonaws.com/nandovieira/media/making-presentations/rubyconf-mindmap.png">
  <img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/rubyconf-mindmap-thumb.png" alt="Mindmap da minha palestra 'Criando aplicações mais fáceis de manter com Ruby on Rails'">
</a></p>

<p>Me arrependo de não ter usado mindmaps antes. Eles realmente facilitam o modo como você visualiza os tópicos pertinentes à sua palestra. Você pode adicionar tudo o que pretende falar (ou não), de um modo rápido e que servirá como guia para a produção dos seus slides depois.</p>

<p>Mesmo que você conheça sobre o assunto que vai falar, é importante estudar um pouco mais sobre ele. Além disso, saber como outras pessoas abordam o mesmo assunto podem dar algumas ideias novas. <a href="https://confreaks.tv">Confreaks</a>, <a href="http://speakerdeck.com">Speaker Deck</a> e <a href="http://slideshare.com">SlideShare</a> são excelentes fontes de pesquisa que uso frequentemente com este intuito.</p>

<p>Blog posts também são ótimos para explorar ideias sobre o assunto que você vai falar, servindo como confirmação de algo que você acredita, como contra-ponto ou até como objeto de crítica. Quando estou pesquisando para palestras, acabo com centenas de abas abertas em meu navegador, então use um navegador separado para fazer essas pesquisas.</p>

<h3>Organizando o texto</h3>

<p><strong>Seus slides devem ter poucas palavras.</strong> Evite cair na armadilha de ter escrito tudo o que você vai falar de qualquer modo. Em vez de ter diversos <em>bullet points</em> no seu slide, tenha vários slides com uma frase que sumariza o que você quer dizer.</p>

<p>Veja os slide à seguir, de uma palestra sobre <a href="http://www.slideshare.net/ihower/distributed-ruby-and-rails">aplicações distribuídas escritas em Ruby</a>:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/slides-bullet-points.png" alt="Slides com bullet points" class="transparent"></p>

<p>Eles poderiam ter sido apresentados de uma maneira muito melhor, mesmo que a sua fala seja exatamente igual.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/slides-no-bullet-points.png" alt="Slides sem bullet points" class="transparent"></p>

<p>No exemplo acima, basta mudar os slides mais rapidamente para explicar cada um dos tópicos, em vez de ficar parado com todo aquele texto na tela. A maior vantagem para esta abordagem é que os participantes não irão despender muito tempo lendo o slide e poderão se concentrar em ouvir o que você tem a dizer.</p>

<h3>Pensando no design de seus slides</h3>

<p>Quem não gosta de ver uma apresentação bem feita, com slides bonitos que mostram o cuidado que a pessoa dedicou ao fazê-los? Eu sim! Então saiba que não é preciso ser um designer experiente para deixá-los visualmente interessantes, como você pode ver nas dicas à seguir.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/slide-detail.jpg" alt="Eu palestrando no 16º Encontro Locaweb" class="transparent align-right"></p>

<p><strong>Use fontes grandes.</strong> Use um tamanho que seja confortável para ler de qualquer lugar da sala. Para que isso funcione bem, siga a dica anterior, aquela de usar pouco texto nos slides.</p>

<p><strong>Use uma fonte que possua diferentes variações de pesos.</strong> Assim você mantém o design coerente, permitindo mudar o estilo quando precisar. Eu recomendo a fonte <a href="http://www.fontsquirrel.com/fonts/open-sans">OpenSans</a>, distribuída gratuitamente. Use &ldquo;OpenSans Extrabold&rdquo; para definir títulos ou frases importantes. Use &ldquo;OpenSans Regular&rdquo; para definir outros textos.</p>

<p><strong>Use sempre cores que tenham bastante contraste.</strong> Projetores dificilmente terão resolução suficiente para exibir adequadamente uma esquema de cores de baixo contraste. E mesmo que tenham, é muito provável que a iluminação do  lugar desfavoreça o seu uso de cores. Você não precisa usar um esquema de cores super elaborado. Veja como é possível obter diversos temas diferentes simples apenas mudando a cor de fundo do slide:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/colorscheme.png" alt="Diferentes cores de slides" class="transparent"></p>

<p>Na dúvida, use o bom preto (cor do texto) no branco (cor de fundo).</p>

<p><strong>Crie seus slides para telas widescreen.</strong> Hoje tenho feito todos os meus slides em 1280x720 (720p). Não me lembro de quando foi a última vez que usei um projetor que não suportava esta resolução. A vantagem é que seu slide ocupará todo o espaço útil disponível para a projeção.</p>

<p>Garanta que seu display está configurado corretamente para apresentar nesta resolução. No Mac, você pode ir em <code>System Preferences</code> e, então, <code>Displays</code>. Selecione a resolução <code>720p</code> ou <code>1280x720</code>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/720p-resolution.png" alt="Preferências de monitor: Resolução de 720p" class="transparent"></p>

<p>Se estas resoluções não forem listadas, pressione a tecla <code>option</code> e clique na opção <code>Scaled</code>; isso exibirá todas as opções disponíveis para aquele projetor.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/list-all-resolutions.png" alt="Lista de resoluções disponíveis no Mac" class="transparent"></p>

<p><strong>Use fotos do tamanho do slide</strong> e adicione o seu texto como uma camada sobre esta imagem. Fotos são excelentes para ilustrar ideias e deixar os seus slides mais bonitos. Também são uma ótima maneira para ilustrar frases que você está citando. Veja como um slide falando sobre a famosa frase de Steve Jobs:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/quote-poor.jpg" alt="Foto com tamanho normal para ilustrar uma citação"></p>

<p>Se você usar uma imagem melhor que ocupe todo o tamanho do slide, terá uma apresentação que causará muito mais impacto:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/quote.jpg" alt="Foto usando todo o tamanho do slide para ilustrar uma citação" class="transparent"></p>

<p>Nem sempre é possível escrever o texto diretamente sobre a imagem; nestes casos, use uma caixa com um fundo preto (talvez até com 90% de opacidade) para que seja fácil distinguir o que está escrito.</p>

<p><strong>Use animações para mostrar exemplos mais claramente.</strong> Animações devem ser usadas para um propósito. Animações entre slides devem ser rápidas e sem muita firula.</p>

<h3>Trabalhando na narrativa</h3>

<p>As melhores apresentações contam histórias. Elas iniciam com um problema, desenvolvem o assunto durante a apresentação e, por fim, encerram com uma conclusão (mesmo que a conclusão seja não ter concluído nada).</p>

<p>Tente ligar a narrativa dos slides de modo que você fazer a transição entre eles sem pausas bruscas.</p>

<p><strong>Inicie a apresentação.</strong> Aqui normalmente começo dizendo algo como &ldquo;Olá, tudo bem? Hoje vou falar um pouco sobre [assunto].&rdquo;. Não se apresente aqui ainda. Você já pode iniciar com um pouco de contexto, explicando porque este assunto é relevante.</p>

<p><strong>Fale um pouco sobre você.</strong> Faça uma apresentação bem rápida e sucinta. Não fale o seu currículo inteiro, pois parecerá que você apenas está se gabando de suas conquistas (mesmo que sejam besteira pura, como certificações). Do mesmo modo, não perca muito tempo apresentando sua empresa; os participantes não estão ali para ouvir seu spam comercial.</p>

<p><strong>Escopo.</strong> Aqui é preciso prender a atenção dos participantes. Fale sobre os problemas que sua palestra tentará elucidar. Faça declarações que você irá detalhar posteriormente. Faça com que o suspense mantenha os participantes envolvidos com a palestra.</p>

<p><strong>Faça sua apresentação.</strong> Aqui não tem muito segredo a não ser praticar o que você pretende falar. Pense em pausas na fala, o modo como você pretende abordar o tema e, se for você for bom nisso, como pretende encaixar piadas.</p>

<p>Se você for apresentar códigos, utilize um tema claro, ou os participantes podem não conseguir ler por causa do contraste do projetor. Também não adianta nada usar fontes tamanho 11 apenas para adicionar mais código ao slide.</p>

<p>Crie pequenos blocos de código, com syntax highlight, suficientes para dar o contexto; o código não precisa ser funcional. Se o seu editor não tiver suporte para copiar código como RTF (no Sublime Text, tem a extensão <a href="https://github.com/n1k0/SublimeHighlight">SublimeHighlight</a>), use o <a href="http://pygments.org">pygments</a>. No Mac, você pode usar algo como o comando abaixo:</p>
<div class="highlight"><pre class="highlight plaintext"><code>pbpaste | pygmentize -l ruby -f rtf -O style=friendly | pbcopy
</code></pre></div>
<p>Se você pretende fazer live coding, pense duas vezes. A probabilidade de algo dar errado é muito grande. Em vez disso, prefira gravar um screencast, sem voz mesmo, e faça a narração no momento da palestra. Faça isso também se você precisa de conexão com a Internet. Acreditar que você terá uma rede disponível com banda suficiente para você é apostar no incerto.</p>

<p>Ao fazer um screencast, grave apenas a janela do programa que você está utilizando. Lembre-se de utilizar um tamanho menor que a resolução dos slides (1280x720) e, em hipótese alguma, grave a tela na resolução total.</p>

<p>De uma forma geral, para códigos, utilize uma fonte monospace com tamanho 20 ou superior. O mesmo se aplica ao tamanho do terminal em caso de screencasts.</p>

<p><strong>Faça um recap.</strong> Palestras técnicas podem ser muito densas. Por isso, coloque em suas conclusões finais os pontos que você quer que os participantes levem de sua palestra.</p>

<p><strong>Encerre a apresentação.</strong> Ao encerrar sua conclusão, é hora de agradecer pelo tempo. Aqui eu gosto de terminar com um slide escrito &ldquo;Obrigado&rdquo;. Normalmente falo algo como &ldquo;Bom, era isso que eu tinha para fazer para vocês. Obrigado.&rdquo;. Ao fazer isso, não fica aquele silêncio constrangedor, já que este tipo de frase fará com que os participantes aplaudam o apresentador.</p>

<p><strong>Abra para perguntas.</strong> Assim que os aplausos cessarem, abra para perguntas se o tempo permitir (e se fizer sentido). Lembre-se de responder sinceramente às perguntas e, caso você não saiba algo, não tenha vergonha de dizer isso. Muitas perguntas assumem um contexto que você não tem a menor ideia. É melhor jogar aberto e dizer que não sabe como algo funcionará neste contexto do que falar uma besteira e ficar marcado por isso para sempre.</p>

<h2>Fazendo a apresentação</h2>

<h3>Controle e adaptadores</h3>

<p>Compre um controle remoto para fazer sua apresentação. Eu tenho um <a href="http://www.kalunga.com.br/prod/apresentador-s-fio-multimidia-usb-cordlles-r400-logitech/004464?menuID=43&WT.svl=4">Logitech R400</a> e ele funciona muito bem, sem ser muito caro. Verifique um dia antes da palestra se a pilha ainda tem carga. Na dúvida, tenha sempre pilhas novas de backup na sua mochila.</p>

<p>Lembre-se também de levar adaptadores de vídeo, principalmente se você tem um Macbook. A maioria dos eventos assume que você seu notebook tem entrada VGA ou HDMI e você terá que usar um notebook emprestado. Tenha um adaptador de cada tipo (você consegue encontrar estes adaptadores no Mercado Livre por volta de R$30 cada).</p>

<h3>Gravando sua palestra</h3>

<p>Muitos eventos não disponibilizam as gravações da conferência. Uma coisa que você pode fazer é gravar sua palestra e disponibilizar depois.</p>

<p>Na conferência Web.br deste ano, nem todas as palestras foram gravadas. Então, para evitar que o conteúdo se perdesse, decidi gravar a tela enquanto fazia a apresentação. Para que o áudio ficasse com uma qualidade boa, usei um <a href="http://www.amazon.com/gp/product/B00N0EPZU8/ref=as_li_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=B00N0EPZU8&linkCode=as2&tag=simpidei-20&linkId=IVHWWS7MHKAZSEFR">microfone de lapela</a> que fez a gravação do áudio separada no meu iPhone. Depois sincronizei tudo no ScreenFlow. <a href="https://www.youtube.com/watch?v=eNPD6CV0RNA">O resultado foi excelente</a>.</p>

<iframe class="video" width="720" height="405" src="https://www.youtube.com/embed/eNPD6CV0RNA" frameborder="0" allowfullscreen></iframe>

<p>Ao fazer isso você também aumenta o público que sua palestra pode alcançar. Lembre-se que muitas pessoas podem não ter os recursos necessários para participar de eventos em outros estados, e assim você irá disseminar o conhecimento com mais abrangência.</p>

<p>Você não precisa gastar dinheiro com um microfone como este se não quiser. Os fones de ouvido com microfone que vem com seu smartphone consegue fazer o trabalho muito bem. Basta achar uma maneira de deixar o microfone preso na sua roupa.</p>

<p>Por não ter seguido esta dica antes, dezenas de palestras que dei se perderam. Não deixe que isso aconteça com você também.</p>

<h3>Configurando a tela do apresentador</h3>

<p>O <a href="http://www.apple.com/mac/keynote/">Keynote</a> permite configurar uma tela de apresentador. Use-a a seu favor. Coloque anotações que permitam lembrar números sobre pesquisas, nomes de pessoas, datas e/ou outros fatos curiosos que você possa esquecer.</p>

<p>Configure também o tempo restante para sua palestra, para que você possa controlar o tempo dedicado a cada slide. Vale lembrar que não existe um cálculo mágico de tempo por slide e isso depende muito mais de quanto detalhe você quer dedicar e, embora treinar sua palestra dê uma ideia de quanto tempo você precisa, às vezes esse tempo varia porque você decidiu improvisar no palco. Para ativar o modo do apresentador sem estar com um monitor externo, utilize o atalho <code>command-shift-R</code>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/presenter-screen.png" alt="Tela do apresentador"></p>

<p>Configure esta tela da maneira que você acha melhor. Eu gosto de dar destaques para notas, mas saber qual é o próximo slide ajuda bastante na hora de dar continuidade na narrativa de sua apresentação.</p>

<p>Se você pretende usar animações, acostume-se com as indicações que esta tela dá para saber quando a animação está em andamento e qual é a próxima animação.</p>

<p>Como falei sobre atalho de teclado, aqui vai uma dica para usuários de Mac: no Keynote, você pode pressionar <code>/</code> para ver um guia com os atalhos disponíveis.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/presenter-shortcuts.png" alt="Ajuda com atalhos disponíveis"></p>

<p>Aprenda alguns deles, pois são extremamente úteis:</p>

<ul>
<li><code>X</code>: alterna as telas de apresentador e apresentação.</li>
<li><code>H</code>: esconde o Keynote e abre o último programa em execução, ótimo para quem faz live coding.</li>
<li><code>K</code>: pausa/continua vídeo, ótimo para quem tem screencasts e quer explicar um pouco sobre algo que foi exibido antes de continuar.</li>
<li><code>[</code> e <code>]</code>: vai para o item de build anterior/seguinte do seu slide, sem executar animações ou transição de slides.</li>
<li><code>shift+left</code> e <code>shift+right</code>: vai para o slide anterior/seguinte sem executar animações ou transições. Note que cada dos builds será exibido sequencialmente, mas sem animações.</li>
</ul>

<p>Um outro atalho muito útil do Mac é <code>command-F1</code>, que alterna entre o modo estendido de monitores e o modo espelho. Assim você não terá que ir até o <code>Display Settings</code> só para mudar esta opção.</p>

<p>Um lembrete muito importante: nem todas as conferências irão fornecer uma segunda tela de apresentador grande, como televisões no chão. Isso é especialmente verdade para eventos menores. Se você, por algum motivo precisar ler as notas de seus slides, aproxime-se de seu próprio notebook discretamente no slide anterior.</p>

<h3>Outras coisas para ter em mente</h3>

<p><strong>Não faça apresentações sem estar conectado a uma fonte de energia.</strong> Mesmo que carga da bateria esteja completa, o uso de dois monitores consome mais energia e você pode acabar ficando na mão. Seria vergonhoso ter que parar sua apresentação para conectar o seu carregador.</p>

<p><strong>Desabilite seu wi-fi.</strong> Principalmente se você estiver conectado em uma rede. Você pode receber notificações de redes sociais ou ter problemas de rede (colisão de endereço IP, por exemplo), onde um alerta pode tirar o foco da apresentação.</p>

<p><strong>Desabilite o protetor de telas.</strong> Se for usar uma apresentação feita em HTML, lembre-se de desabilitar o protetor de telas. Isso normalmente não é um problema para quem usar programas como Keynote ou Powerpoint. De qualquer modo, você pode instalar programas como <a href="https://itunes.apple.com/us/app/should-i-sleep/id560851219?mt=12">Should I Sleep</a> ou <a href="https://itunes.apple.com/us/app/caffeine/id411246225?mt=12">Caffeine</a> para garantir que seu notebook não irá ativar o protetor de telas.</p>

<p><strong>Coloque seu telefone em modo avião.</strong> Para evitar distrações, é melhor simplesmente silenciar seu telefone. Assim, se alguém te ligar ou mencionar você em alguma rede social, não perderá o foco.</p>

<h3>Chegou o grande dia</h3>

<p>Finalmente chegou o grande dia. O nervosismo pode bater na porta e você acha que vai fazer uma palestra ruim. Primeiro, acalme-se (ou pelo menos tente). Todo mundo fica nervoso, até mesmo os palestrantes mais experientes. A única solução para isso é se preparar e&hellip; fazer mais palestras!</p>

<p><strong>Palestre o máximo que você puder em grupos de usuários locais</strong> antes de palestrar em conferências maiores. Se a sua cidade não possui nenhum desses grupos, aproveite a oportunidade e comece um. Faça apresentações na sua empresa, pedindo sugestões. Apresente para ninguém, falando sozinho como se fosse louco. O importante é apresentar de algum modo, para sentir o que pode ser melhorado.</p>

<p><strong>Treine diferentes tipos de entonação.</strong> Isso prenderá a atenção de quem estiver assistindo. Sua voz pode ser usada para ditar o ritmo de sua palestra. Falar mais rápido pode ser usado para trazer energia para algo que você acha que é empolgante. Pausas são ótimas para dar ênfase a algo que você acabou de falar. Falar mais devagar pode ser usado em momentos que você queira que as pessoas prestem mais atenção. Acima de tudo, não deixe o tom monótono ditar sua palesta.</p>

<p><strong>Gerenciando o dano.</strong> Eventualmente, as palavras sairão de sua boca de um modo que não era sua intenção. Você vai, sem intenção, ofender pessoas. Você vai esquecer o que tinha para falar em um determinado&hellip; é, deu branco! Ou ainda, contar uma história longa e chata que só depois que começou a contar é que percebeu que era melhor não ter dito nada. Pois é, isso acontece.</p>

<p>A melhor maneira para lidar com essas situações é continuar sua apresentação, sem dar muito foco para essas coisas. Termine rapidamente o que você acha que não está funcionando, e continue sua apresentação. Se for algo muito ruim que merece retratações, faça isso depois que a palestra terminar em um blog post ou sua rede social favorita.</p>

<h2>Finalizando</h2>

<p>Palestrar e dar a cara a tapa não é para todo mundo. Se você quer palestrar, lembre-se que pessoas irão criticar você. Lembre-se que, eventualmente, você vai cometer algum deslize e vai falar besteira. Lembre-se que muitas pessoas estarão em seus notebooks/smartphones, mas não se sinta ofendido por isso. Lembre-se também que pessoas vidradas, sem se mexerem muito pode ser um sinal de que sua palestra está muito interessante, mesmo quando seu cérebro diz o contrário.</p>

<p>Tente manter a calma, não importa qual seja a reação do público. Se você acha que a reação das pessoas pode afetar a sua performance, olhe sempre em frente, acima das cabeças dos participantes sem se preocupar com o que você <em>acha</em> que eles estão pensando.</p>

<p>O nervosismo que você sente, é o mesmo para todos. A diferença é que com experiência, você conseguirá gerenciá-lo melhor. Com a experiência, seu nervosismo irá embora logo nos primeiros slides. Por isso palestrar mais vezes é importante.</p>

<p>Fazer uma palestra exige muito mais dedicação do que você pensa que é necessário, mas no fim, vale muito a pena. Parabéns por dar esse importantíssimo passo em sua carreira e na disseminação de conhecimento. Mas não é só isso.</p>

<p>Palestrar em conferências é uma excelente maneira de conhecer pessoas interessantes. Afinal, tem bastante gente para interagir dentre os organizadores, palestrantes e participantes da conferência.
​<figure>
  <img class="transparent" src="https://s3.amazonaws.com/nandovieira/media/making-presentations/encontro-locaweb-rj.jpg" alt="Foto com Almir Filho, Jaydson Gomes, Jean Carlo Emer e alguns participantes — 16º Encontro Locaweb (Rio de Janeiro)">
  <figcaption>Foto com Almir Filho, Jaydson Gomes, Jean Carlo Emer e alguns participantes<br>16º Encontro Locaweb (Rio de Janeiro)</figcaption>
</figure></p>

<h3>Resources</h3>

<ul>
<li><a href="http://marcgrabanski.com/tech-talks/">10 Things I Wish I’d Known About Giving Good Tech Talks</a></li>
<li><a href="http://blog.visme.co/7-storytelling-techniques-used-by-the-most-inspiring-ted-presenters/">7 Storytelling Techniques Used by the Most Inspiring TED Presenters</a></li>
<li><a href="http://www.sparkol.com/engage/8-classic-storytelling-techniques-for-engaging-presentations/">8 Classic storytelling techniques for engaging presentations</a></li>
<li><a href="http://www.akitaonrails.com/2010/10/31/off-topic-dicas-de-mac-para-palestrantes">Dicas de Mac para palestrantes</a></li>
<li><a href="http://www.walkingpaper.org/695">How to give a good presentation</a></li>
<li><a href="https://medium.com/@juliangruber/how-to-give-good-live-coding-talks-3fd2f1371ace">How to give good live coding talks</a></li>
<li><a href="http://writing.jan.io/2013/05/10/how-to-give-the-killer-tech-talk---a-pamphlet.html">How To Give the Killer Tech Talk — A Pamphlet</a></li>
<li><a href="http://www.ted.com/topics/storytelling">Lista de palestras sobre Storytelling do TED</a></li>
<li><a href="http://blog.jayfields.com/2009/12/presenting-lessons-ive-learned.html">Presenting Lessons I&rsquo;ve Learned</a></li>
<li><a href="http://zachholman.com/posts/slide-design-for-developers/">Slide Design for Developers</a></li>
<li><a href="http://speaking.io">Speaking: Because &ldquo;imagine everyone&rsquo;s naked&rdquo; is terrible advice</a></li>
<li><a href="http://www.ted.com/talks/nancy_duarte_the_secret_structure_of_great_talks">The secret structure of great talks</a></li>
<li><a href="http://zachholman.com/posts/what-they-dont-tell-you-about-public-speaking/">What They Don&rsquo;t Tell You About Public Speaking</a></li>
<li><a href="https://inconshreveable.com/11-13-2015/your-live-coding-demo-is-boring/">Your live coding demo is boring</a></li>
</ul>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/eu-palestrando.jpg" alt="Eu palestrando no 16º Encontro Locaweb" class="transparent"></p>

<p>Muitas pessoas me perguntam como se tornar um palestrante ou como fazer slides melhores com uma certa frequência. Eu normalmente dou algumas dicas, mas não dou detalhes, pois é um assunto complexo.</p>

<p>Então, desta vez decidi documentar neste artigo uma série de dicas que podem auxiliar você nessa difícil tarefa que é entregar conteúdo útil em uma palestra com tempo limitado.</p>

<h2>Escolhendo o tema</h2>

<p>Escolher o tema do que você quer palestrar é uma das tarefas mais difíceis. O maior problema é que <em>achamos que não temos nada interessante para falar</em>. E isso simplesmente não é verdade!</p>

<p>Sempre existirão pessoas mais experientes que você em eventos, assim como também sempre existirão pessoas que estão começando e que são menos experientes. Por isso, mesmo que você julgue seu tema como sendo básico, ele pode ser avançado e interessante o bastante para estas pessoas.</p>

<p>Além disso, mesmo que seja um assunto que já foi exaurido, você pode dar uma nova perspectiva sobre o assunto. Basta não falar o que já foi dito extensivamente por outras pessoas.</p>

<p>Um problema muito comum é sobre o escopo de sua palestra. Tenha em mente que o tempo reduzido tornará difícil a tarefa de abordar um escopo muito grande. Por isso, experimente focar em uma parte do conteúdo em vez de tentar abraçar o mundo.</p>

<h3>Falando sobre algo que você domina</h3>

<p>Se você está dando sua primeira palestra, escolher um assunto que você domina é uma excelente escolha. Isso porque você poderá se dedicar em aprender como palestrar melhor, em vez de também ter que aprender sobre o assunto da palestra.</p>

<p>Estas palestras podem beneficiar pessoas experientes e inexperientes. Para pessoas experientes, você pode falar sobre dicas e assuntos mais avançados. No caso de pessoas inexperientes, você pode dar dicas que como ter um início mais sólido, tirando da frente problemas que você mesmo já enfrentou.</p>

<h3>Falando sobre algo que você tem interesse</h3>

<p>Às vezes você decide por falar sobre algo que você tem interesse. Dar uma palestra sobre este assunto é uma excelente maneira de se aprofundar e aprender mais sobre ele. Penso que é melhor fazer isso quando você já tem uma certa experiência em dar palestras, já que você precisará se preocupar em aprender mais sobre o que vai falar.</p>

<p>Normalmente são palestras apaixonadas e que podem estimular os participantes por causa de sua empolgação sobre aquele assunto.</p>

<h3>Falando sobre algo que é popular</h3>

<p>Uma excelente maneira de garantir que sua palestra seja aceita em eventos é falar sobre assuntos populares do momento. Neste caso, você pode abordar o assunto sobre a ótica de porque ele é mais relevante que uma tecnologia concorrente ou porque alguém deveria considerá-lo para começo de conversa.</p>

<h3>Falando sobre algo que é importante</h3>

<p>Muitos assuntos são de suma importância e também tem grandes chances de fazerem sucesso em conferências. Eu, por exemplo, gosto muito de falar sobre segurança em aplicações web, pois mesmo sendo um assunto essencial e muito apresentado, ainda não é algo que todos dominam (e que muitos desconhecem totalmente).</p>

<p>Quando apresentadas com um tom de urgência, podem estimular os participantes a procurarem mais sobre o assunto após a palestra.</p>

<h2>Preparando sua palestra</h2>

<p>Depois de escolher o tema, é hora de preparar sua palestra. Ultimamente tenho usado <a href="https://mindnode.com">mindmaps</a> para organizar as minhas ideias. Veja, por exemplo, como ficou o mindmap da minha palestra da Rubyconf Brasil 2015.</p>

<p><a href="https://s3.amazonaws.com/nandovieira/media/making-presentations/rubyconf-mindmap.png">
  <img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/rubyconf-mindmap-thumb.png" alt="Mindmap da minha palestra 'Criando aplicações mais fáceis de manter com Ruby on Rails'">
</a></p>

<p>Me arrependo de não ter usado mindmaps antes. Eles realmente facilitam o modo como você visualiza os tópicos pertinentes à sua palestra. Você pode adicionar tudo o que pretende falar (ou não), de um modo rápido e que servirá como guia para a produção dos seus slides depois.</p>

<p>Mesmo que você conheça sobre o assunto que vai falar, é importante estudar um pouco mais sobre ele. Além disso, saber como outras pessoas abordam o mesmo assunto podem dar algumas ideias novas. <a href="https://confreaks.tv">Confreaks</a>, <a href="http://speakerdeck.com">Speaker Deck</a> e <a href="http://slideshare.com">SlideShare</a> são excelentes fontes de pesquisa que uso frequentemente com este intuito.</p>

<p>Blog posts também são ótimos para explorar ideias sobre o assunto que você vai falar, servindo como confirmação de algo que você acredita, como contra-ponto ou até como objeto de crítica. Quando estou pesquisando para palestras, acabo com centenas de abas abertas em meu navegador, então use um navegador separado para fazer essas pesquisas.</p>

<h3>Organizando o texto</h3>

<p><strong>Seus slides devem ter poucas palavras.</strong> Evite cair na armadilha de ter escrito tudo o que você vai falar de qualquer modo. Em vez de ter diversos <em>bullet points</em> no seu slide, tenha vários slides com uma frase que sumariza o que você quer dizer.</p>

<p>Veja os slide à seguir, de uma palestra sobre <a href="http://www.slideshare.net/ihower/distributed-ruby-and-rails">aplicações distribuídas escritas em Ruby</a>:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/slides-bullet-points.png" alt="Slides com bullet points" class="transparent"></p>

<p>Eles poderiam ter sido apresentados de uma maneira muito melhor, mesmo que a sua fala seja exatamente igual.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/slides-no-bullet-points.png" alt="Slides sem bullet points" class="transparent"></p>

<p>No exemplo acima, basta mudar os slides mais rapidamente para explicar cada um dos tópicos, em vez de ficar parado com todo aquele texto na tela. A maior vantagem para esta abordagem é que os participantes não irão despender muito tempo lendo o slide e poderão se concentrar em ouvir o que você tem a dizer.</p>

<h3>Pensando no design de seus slides</h3>

<p>Quem não gosta de ver uma apresentação bem feita, com slides bonitos que mostram o cuidado que a pessoa dedicou ao fazê-los? Eu sim! Então saiba que não é preciso ser um designer experiente para deixá-los visualmente interessantes, como você pode ver nas dicas à seguir.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/slide-detail.jpg" alt="Eu palestrando no 16º Encontro Locaweb" class="transparent align-right"></p>

<p><strong>Use fontes grandes.</strong> Use um tamanho que seja confortável para ler de qualquer lugar da sala. Para que isso funcione bem, siga a dica anterior, aquela de usar pouco texto nos slides.</p>

<p><strong>Use uma fonte que possua diferentes variações de pesos.</strong> Assim você mantém o design coerente, permitindo mudar o estilo quando precisar. Eu recomendo a fonte <a href="http://www.fontsquirrel.com/fonts/open-sans">OpenSans</a>, distribuída gratuitamente. Use &ldquo;OpenSans Extrabold&rdquo; para definir títulos ou frases importantes. Use &ldquo;OpenSans Regular&rdquo; para definir outros textos.</p>

<p><strong>Use sempre cores que tenham bastante contraste.</strong> Projetores dificilmente terão resolução suficiente para exibir adequadamente uma esquema de cores de baixo contraste. E mesmo que tenham, é muito provável que a iluminação do  lugar desfavoreça o seu uso de cores. Você não precisa usar um esquema de cores super elaborado. Veja como é possível obter diversos temas diferentes simples apenas mudando a cor de fundo do slide:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/colorscheme.png" alt="Diferentes cores de slides" class="transparent"></p>

<p>Na dúvida, use o bom preto (cor do texto) no branco (cor de fundo).</p>

<p><strong>Crie seus slides para telas widescreen.</strong> Hoje tenho feito todos os meus slides em 1280x720 (720p). Não me lembro de quando foi a última vez que usei um projetor que não suportava esta resolução. A vantagem é que seu slide ocupará todo o espaço útil disponível para a projeção.</p>

<p>Garanta que seu display está configurado corretamente para apresentar nesta resolução. No Mac, você pode ir em <code>System Preferences</code> e, então, <code>Displays</code>. Selecione a resolução <code>720p</code> ou <code>1280x720</code>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/720p-resolution.png" alt="Preferências de monitor: Resolução de 720p" class="transparent"></p>

<p>Se estas resoluções não forem listadas, pressione a tecla <code>option</code> e clique na opção <code>Scaled</code>; isso exibirá todas as opções disponíveis para aquele projetor.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/list-all-resolutions.png" alt="Lista de resoluções disponíveis no Mac" class="transparent"></p>

<p><strong>Use fotos do tamanho do slide</strong> e adicione o seu texto como uma camada sobre esta imagem. Fotos são excelentes para ilustrar ideias e deixar os seus slides mais bonitos. Também são uma ótima maneira para ilustrar frases que você está citando. Veja como um slide falando sobre a famosa frase de Steve Jobs:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/quote-poor.jpg" alt="Foto com tamanho normal para ilustrar uma citação"></p>

<p>Se você usar uma imagem melhor que ocupe todo o tamanho do slide, terá uma apresentação que causará muito mais impacto:</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/quote.jpg" alt="Foto usando todo o tamanho do slide para ilustrar uma citação" class="transparent"></p>

<p>Nem sempre é possível escrever o texto diretamente sobre a imagem; nestes casos, use uma caixa com um fundo preto (talvez até com 90% de opacidade) para que seja fácil distinguir o que está escrito.</p>

<p><strong>Use animações para mostrar exemplos mais claramente.</strong> Animações devem ser usadas para um propósito. Animações entre slides devem ser rápidas e sem muita firula.</p>

<h3>Trabalhando na narrativa</h3>

<p>As melhores apresentações contam histórias. Elas iniciam com um problema, desenvolvem o assunto durante a apresentação e, por fim, encerram com uma conclusão (mesmo que a conclusão seja não ter concluído nada).</p>

<p>Tente ligar a narrativa dos slides de modo que você fazer a transição entre eles sem pausas bruscas.</p>

<p><strong>Inicie a apresentação.</strong> Aqui normalmente começo dizendo algo como &ldquo;Olá, tudo bem? Hoje vou falar um pouco sobre [assunto].&rdquo;. Não se apresente aqui ainda. Você já pode iniciar com um pouco de contexto, explicando porque este assunto é relevante.</p>

<p><strong>Fale um pouco sobre você.</strong> Faça uma apresentação bem rápida e sucinta. Não fale o seu currículo inteiro, pois parecerá que você apenas está se gabando de suas conquistas (mesmo que sejam besteira pura, como certificações). Do mesmo modo, não perca muito tempo apresentando sua empresa; os participantes não estão ali para ouvir seu spam comercial.</p>

<p><strong>Escopo.</strong> Aqui é preciso prender a atenção dos participantes. Fale sobre os problemas que sua palestra tentará elucidar. Faça declarações que você irá detalhar posteriormente. Faça com que o suspense mantenha os participantes envolvidos com a palestra.</p>

<p><strong>Faça sua apresentação.</strong> Aqui não tem muito segredo a não ser praticar o que você pretende falar. Pense em pausas na fala, o modo como você pretende abordar o tema e, se for você for bom nisso, como pretende encaixar piadas.</p>

<p>Se você for apresentar códigos, utilize um tema claro, ou os participantes podem não conseguir ler por causa do contraste do projetor. Também não adianta nada usar fontes tamanho 11 apenas para adicionar mais código ao slide.</p>

<p>Crie pequenos blocos de código, com syntax highlight, suficientes para dar o contexto; o código não precisa ser funcional. Se o seu editor não tiver suporte para copiar código como RTF (no Sublime Text, tem a extensão <a href="https://github.com/n1k0/SublimeHighlight">SublimeHighlight</a>), use o <a href="http://pygments.org">pygments</a>. No Mac, você pode usar algo como o comando abaixo:</p>
<div class="highlight"><pre class="highlight plaintext"><code>pbpaste | pygmentize -l ruby -f rtf -O style=friendly | pbcopy
</code></pre></div>
<p>Se você pretende fazer live coding, pense duas vezes. A probabilidade de algo dar errado é muito grande. Em vez disso, prefira gravar um screencast, sem voz mesmo, e faça a narração no momento da palestra. Faça isso também se você precisa de conexão com a Internet. Acreditar que você terá uma rede disponível com banda suficiente para você é apostar no incerto.</p>

<p>Ao fazer um screencast, grave apenas a janela do programa que você está utilizando. Lembre-se de utilizar um tamanho menor que a resolução dos slides (1280x720) e, em hipótese alguma, grave a tela na resolução total.</p>

<p>De uma forma geral, para códigos, utilize uma fonte monospace com tamanho 20 ou superior. O mesmo se aplica ao tamanho do terminal em caso de screencasts.</p>

<p><strong>Faça um recap.</strong> Palestras técnicas podem ser muito densas. Por isso, coloque em suas conclusões finais os pontos que você quer que os participantes levem de sua palestra.</p>

<p><strong>Encerre a apresentação.</strong> Ao encerrar sua conclusão, é hora de agradecer pelo tempo. Aqui eu gosto de terminar com um slide escrito &ldquo;Obrigado&rdquo;. Normalmente falo algo como &ldquo;Bom, era isso que eu tinha para fazer para vocês. Obrigado.&rdquo;. Ao fazer isso, não fica aquele silêncio constrangedor, já que este tipo de frase fará com que os participantes aplaudam o apresentador.</p>

<p><strong>Abra para perguntas.</strong> Assim que os aplausos cessarem, abra para perguntas se o tempo permitir (e se fizer sentido). Lembre-se de responder sinceramente às perguntas e, caso você não saiba algo, não tenha vergonha de dizer isso. Muitas perguntas assumem um contexto que você não tem a menor ideia. É melhor jogar aberto e dizer que não sabe como algo funcionará neste contexto do que falar uma besteira e ficar marcado por isso para sempre.</p>

<h2>Fazendo a apresentação</h2>

<h3>Controle e adaptadores</h3>

<p>Compre um controle remoto para fazer sua apresentação. Eu tenho um <a href="http://www.kalunga.com.br/prod/apresentador-s-fio-multimidia-usb-cordlles-r400-logitech/004464?menuID=43&WT.svl=4">Logitech R400</a> e ele funciona muito bem, sem ser muito caro. Verifique um dia antes da palestra se a pilha ainda tem carga. Na dúvida, tenha sempre pilhas novas de backup na sua mochila.</p>

<p>Lembre-se também de levar adaptadores de vídeo, principalmente se você tem um Macbook. A maioria dos eventos assume que você seu notebook tem entrada VGA ou HDMI e você terá que usar um notebook emprestado. Tenha um adaptador de cada tipo (você consegue encontrar estes adaptadores no Mercado Livre por volta de R$30 cada).</p>

<h3>Gravando sua palestra</h3>

<p>Muitos eventos não disponibilizam as gravações da conferência. Uma coisa que você pode fazer é gravar sua palestra e disponibilizar depois.</p>

<p>Na conferência Web.br deste ano, nem todas as palestras foram gravadas. Então, para evitar que o conteúdo se perdesse, decidi gravar a tela enquanto fazia a apresentação. Para que o áudio ficasse com uma qualidade boa, usei um <a href="http://www.amazon.com/gp/product/B00N0EPZU8/ref=as_li_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=B00N0EPZU8&linkCode=as2&tag=simpidei-20&linkId=IVHWWS7MHKAZSEFR">microfone de lapela</a> que fez a gravação do áudio separada no meu iPhone. Depois sincronizei tudo no ScreenFlow. <a href="https://www.youtube.com/watch?v=eNPD6CV0RNA">O resultado foi excelente</a>.</p>

<iframe class="video" width="720" height="405" src="https://www.youtube.com/embed/eNPD6CV0RNA" frameborder="0" allowfullscreen></iframe>

<p>Ao fazer isso você também aumenta o público que sua palestra pode alcançar. Lembre-se que muitas pessoas podem não ter os recursos necessários para participar de eventos em outros estados, e assim você irá disseminar o conhecimento com mais abrangência.</p>

<p>Você não precisa gastar dinheiro com um microfone como este se não quiser. Os fones de ouvido com microfone que vem com seu smartphone consegue fazer o trabalho muito bem. Basta achar uma maneira de deixar o microfone preso na sua roupa.</p>

<p>Por não ter seguido esta dica antes, dezenas de palestras que dei se perderam. Não deixe que isso aconteça com você também.</p>

<h3>Configurando a tela do apresentador</h3>

<p>O <a href="http://www.apple.com/mac/keynote/">Keynote</a> permite configurar uma tela de apresentador. Use-a a seu favor. Coloque anotações que permitam lembrar números sobre pesquisas, nomes de pessoas, datas e/ou outros fatos curiosos que você possa esquecer.</p>

<p>Configure também o tempo restante para sua palestra, para que você possa controlar o tempo dedicado a cada slide. Vale lembrar que não existe um cálculo mágico de tempo por slide e isso depende muito mais de quanto detalhe você quer dedicar e, embora treinar sua palestra dê uma ideia de quanto tempo você precisa, às vezes esse tempo varia porque você decidiu improvisar no palco. Para ativar o modo do apresentador sem estar com um monitor externo, utilize o atalho <code>command-shift-R</code>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/presenter-screen.png" alt="Tela do apresentador"></p>

<p>Configure esta tela da maneira que você acha melhor. Eu gosto de dar destaques para notas, mas saber qual é o próximo slide ajuda bastante na hora de dar continuidade na narrativa de sua apresentação.</p>

<p>Se você pretende usar animações, acostume-se com as indicações que esta tela dá para saber quando a animação está em andamento e qual é a próxima animação.</p>

<p>Como falei sobre atalho de teclado, aqui vai uma dica para usuários de Mac: no Keynote, você pode pressionar <code>/</code> para ver um guia com os atalhos disponíveis.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/making-presentations/presenter-shortcuts.png" alt="Ajuda com atalhos disponíveis"></p>

<p>Aprenda alguns deles, pois são extremamente úteis:</p>

<ul>
<li><code>X</code>: alterna as telas de apresentador e apresentação.</li>
<li><code>H</code>: esconde o Keynote e abre o último programa em execução, ótimo para quem faz live coding.</li>
<li><code>K</code>: pausa/continua vídeo, ótimo para quem tem screencasts e quer explicar um pouco sobre algo que foi exibido antes de continuar.</li>
<li><code>[</code> e <code>]</code>: vai para o item de build anterior/seguinte do seu slide, sem executar animações ou transição de slides.</li>
<li><code>shift+left</code> e <code>shift+right</code>: vai para o slide anterior/seguinte sem executar animações ou transições. Note que cada dos builds será exibido sequencialmente, mas sem animações.</li>
</ul>

<p>Um outro atalho muito útil do Mac é <code>command-F1</code>, que alterna entre o modo estendido de monitores e o modo espelho. Assim você não terá que ir até o <code>Display Settings</code> só para mudar esta opção.</p>

<p>Um lembrete muito importante: nem todas as conferências irão fornecer uma segunda tela de apresentador grande, como televisões no chão. Isso é especialmente verdade para eventos menores. Se você, por algum motivo precisar ler as notas de seus slides, aproxime-se de seu próprio notebook discretamente no slide anterior.</p>

<h3>Outras coisas para ter em mente</h3>

<p><strong>Não faça apresentações sem estar conectado a uma fonte de energia.</strong> Mesmo que carga da bateria esteja completa, o uso de dois monitores consome mais energia e você pode acabar ficando na mão. Seria vergonhoso ter que parar sua apresentação para conectar o seu carregador.</p>

<p><strong>Desabilite seu wi-fi.</strong> Principalmente se você estiver conectado em uma rede. Você pode receber notificações de redes sociais ou ter problemas de rede (colisão de endereço IP, por exemplo), onde um alerta pode tirar o foco da apresentação.</p>

<p><strong>Desabilite o protetor de telas.</strong> Se for usar uma apresentação feita em HTML, lembre-se de desabilitar o protetor de telas. Isso normalmente não é um problema para quem usar programas como Keynote ou Powerpoint. De qualquer modo, você pode instalar programas como <a href="https://itunes.apple.com/us/app/should-i-sleep/id560851219?mt=12">Should I Sleep</a> ou <a href="https://itunes.apple.com/us/app/caffeine/id411246225?mt=12">Caffeine</a> para garantir que seu notebook não irá ativar o protetor de telas.</p>

<p><strong>Coloque seu telefone em modo avião.</strong> Para evitar distrações, é melhor simplesmente silenciar seu telefone. Assim, se alguém te ligar ou mencionar você em alguma rede social, não perderá o foco.</p>

<h3>Chegou o grande dia</h3>

<p>Finalmente chegou o grande dia. O nervosismo pode bater na porta e você acha que vai fazer uma palestra ruim. Primeiro, acalme-se (ou pelo menos tente). Todo mundo fica nervoso, até mesmo os palestrantes mais experientes. A única solução para isso é se preparar e&hellip; fazer mais palestras!</p>

<p><strong>Palestre o máximo que você puder em grupos de usuários locais</strong> antes de palestrar em conferências maiores. Se a sua cidade não possui nenhum desses grupos, aproveite a oportunidade e comece um. Faça apresentações na sua empresa, pedindo sugestões. Apresente para ninguém, falando sozinho como se fosse louco. O importante é apresentar de algum modo, para sentir o que pode ser melhorado.</p>

<p><strong>Treine diferentes tipos de entonação.</strong> Isso prenderá a atenção de quem estiver assistindo. Sua voz pode ser usada para ditar o ritmo de sua palestra. Falar mais rápido pode ser usado para trazer energia para algo que você acha que é empolgante. Pausas são ótimas para dar ênfase a algo que você acabou de falar. Falar mais devagar pode ser usado em momentos que você queira que as pessoas prestem mais atenção. Acima de tudo, não deixe o tom monótono ditar sua palesta.</p>

<p><strong>Gerenciando o dano.</strong> Eventualmente, as palavras sairão de sua boca de um modo que não era sua intenção. Você vai, sem intenção, ofender pessoas. Você vai esquecer o que tinha para falar em um determinado&hellip; é, deu branco! Ou ainda, contar uma história longa e chata que só depois que começou a contar é que percebeu que era melhor não ter dito nada. Pois é, isso acontece.</p>

<p>A melhor maneira para lidar com essas situações é continuar sua apresentação, sem dar muito foco para essas coisas. Termine rapidamente o que você acha que não está funcionando, e continue sua apresentação. Se for algo muito ruim que merece retratações, faça isso depois que a palestra terminar em um blog post ou sua rede social favorita.</p>

<h2>Finalizando</h2>

<p>Palestrar e dar a cara a tapa não é para todo mundo. Se você quer palestrar, lembre-se que pessoas irão criticar você. Lembre-se que, eventualmente, você vai cometer algum deslize e vai falar besteira. Lembre-se que muitas pessoas estarão em seus notebooks/smartphones, mas não se sinta ofendido por isso. Lembre-se também que pessoas vidradas, sem se mexerem muito pode ser um sinal de que sua palestra está muito interessante, mesmo quando seu cérebro diz o contrário.</p>

<p>Tente manter a calma, não importa qual seja a reação do público. Se você acha que a reação das pessoas pode afetar a sua performance, olhe sempre em frente, acima das cabeças dos participantes sem se preocupar com o que você <em>acha</em> que eles estão pensando.</p>

<p>O nervosismo que você sente, é o mesmo para todos. A diferença é que com experiência, você conseguirá gerenciá-lo melhor. Com a experiência, seu nervosismo irá embora logo nos primeiros slides. Por isso palestrar mais vezes é importante.</p>

<p>Fazer uma palestra exige muito mais dedicação do que você pensa que é necessário, mas no fim, vale muito a pena. Parabéns por dar esse importantíssimo passo em sua carreira e na disseminação de conhecimento. Mas não é só isso.</p>

<p>Palestrar em conferências é uma excelente maneira de conhecer pessoas interessantes. Afinal, tem bastante gente para interagir dentre os organizadores, palestrantes e participantes da conferência.
​<figure>
  <img class="transparent" src="https://s3.amazonaws.com/nandovieira/media/making-presentations/encontro-locaweb-rj.jpg" alt="Foto com Almir Filho, Jaydson Gomes, Jean Carlo Emer e alguns participantes — 16º Encontro Locaweb (Rio de Janeiro)">
  <figcaption>Foto com Almir Filho, Jaydson Gomes, Jean Carlo Emer e alguns participantes<br>16º Encontro Locaweb (Rio de Janeiro)</figcaption>
</figure></p>

<h3>Resources</h3>

<ul>
<li><a href="http://marcgrabanski.com/tech-talks/">10 Things I Wish I’d Known About Giving Good Tech Talks</a></li>
<li><a href="http://blog.visme.co/7-storytelling-techniques-used-by-the-most-inspiring-ted-presenters/">7 Storytelling Techniques Used by the Most Inspiring TED Presenters</a></li>
<li><a href="http://www.sparkol.com/engage/8-classic-storytelling-techniques-for-engaging-presentations/">8 Classic storytelling techniques for engaging presentations</a></li>
<li><a href="http://www.akitaonrails.com/2010/10/31/off-topic-dicas-de-mac-para-palestrantes">Dicas de Mac para palestrantes</a></li>
<li><a href="http://www.walkingpaper.org/695">How to give a good presentation</a></li>
<li><a href="https://medium.com/@juliangruber/how-to-give-good-live-coding-talks-3fd2f1371ace">How to give good live coding talks</a></li>
<li><a href="http://writing.jan.io/2013/05/10/how-to-give-the-killer-tech-talk---a-pamphlet.html">How To Give the Killer Tech Talk — A Pamphlet</a></li>
<li><a href="http://www.ted.com/topics/storytelling">Lista de palestras sobre Storytelling do TED</a></li>
<li><a href="http://blog.jayfields.com/2009/12/presenting-lessons-ive-learned.html">Presenting Lessons I&rsquo;ve Learned</a></li>
<li><a href="http://zachholman.com/posts/slide-design-for-developers/">Slide Design for Developers</a></li>
<li><a href="http://speaking.io">Speaking: Because &ldquo;imagine everyone&rsquo;s naked&rdquo; is terrible advice</a></li>
<li><a href="http://www.ted.com/talks/nancy_duarte_the_secret_structure_of_great_talks">The secret structure of great talks</a></li>
<li><a href="http://zachholman.com/posts/what-they-dont-tell-you-about-public-speaking/">What They Don&rsquo;t Tell You About Public Speaking</a></li>
<li><a href="https://inconshreveable.com/11-13-2015/your-live-coding-demo-is-boring/">Your live coding demo is boring</a></li>
</ul>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/dicas-para-quem-quer-comecar-a-palestrar</link>
      <guid>https://nandovieira.com.br/dicas-para-quem-quer-comecar-a-palestrar</guid>
      <pubDate>Tue, 01 Dec 2015 10:54:00 -0200</pubDate>
    </item>
    <item>
      <title>Adeus, Simples Ideias!</title>
      <description>
        <![CDATA[<p><img src="https://s3.amazonaws.com/nandovieira/media/nv-logo.png" alt="Nando Vieira" class="align-right transparent" /></p>

<p>Depois de quase 9 anos, chegou a hora de dizer adeus ao Simples Ideias. Mas não se preocupe. O conteúdo continua o mesmo, apenas com um domínio diferente.</p>

<p>Há pouco mais de um ano comecei a escrever em inglês em <a href="http://nandovieira.com">http://nandovieira.com</a>. Não demorou muito para que eu começasse a achar estranho ter dois domínios extremamente diferentes para conteúdos tão semelhantes. Então, como eu já tinha o domínio <a href="http://nandovieira.com.br">http://nandovieira.com.br</a> disponível, foi só uma questão de preparar a migração.</p>

<h2>Como fazer a migração dos domínios</h2>

<p>O primeiro passo foi garantir que todas as URLs estavam sendo redirecionadas corretamente com o <a href="https://en.wikipedia.org/wiki/HTTP_301">status 301</a>. Isso foi bem fácil de fazer com NGINX.</p>
<div class="highlight"><pre class="highlight nginx"><code><span class="k">server</span> <span class="p">{</span>
  <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
  <span class="kn">server_name</span> <span class="s">www.simplesideias.com.br</span><span class="p">;</span>
  <span class="kn">return</span> <span class="mi">301</span> <span class="s">http://nandovieira.com.br</span><span class="nv">$request_uri</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">server</span> <span class="p">{</span>
  <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
  <span class="kn">server_name</span> <span class="s">simplesideias.com.br</span><span class="p">;</span>
  <span class="kn">return</span> <span class="mi">301</span> <span class="s">http://nandovieira.com.br</span><span class="nv">$request_uri</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>Depois, fiz a migração dos comentários no <a href="http://disqus.com">Disqus</a>. Como os redirecionamentos estavam corretos, utilizei a opção <a href="https://help.disqus.com/customer/portal/articles/912834-redirect-crawler">Redirect Crawler</a>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/disqus-migration-tool.png" alt="Disqus Migration Tool"></p>

<p>Finalmente, tive que fazer a migração da busca no <a href="https://www.google.com/webmasters/tools/">Google Webmasters Tool</a>. Após criar uma nova propriedade para o domínio <code>nandovieira.com.br</code>, foi necessário solicitar a <em>mudança de endereço</em>. No domínio original, escolha a opção <em>Mudança de Endereço</em>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/google-webmasters-change-address-1.png" alt="Google Webmasters Tool - Mudar endereço"></p>

<p>Depois, basta seguir cada um dos passos apresentados, para solicitar a migração. No meu caso, este processo ainda está em andamento.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/google-webmasters-change-address-2.png" alt="Google Webmasters Tool - Mudar endereço"></p>

<p>Uma coisa interessante é que mesmo esse processo não tendo sido finalizado, não senti nenhum impacto no número de visitas, segundo o Google Analytics.</p>

<h2>Finalizando</h2>

<p>Era isso! Nenhuma alteração significativa para quem já acompanha o blog. O link do feed também está sendo redirecionado, então tudo deve continuar funcionando sem que você precise alterar nada.</p>

<p>Ah, nesse processo aproveitei e dei uma renovada no visual. Nada radical, mas achei que deu uma modernizada boa.</p>
]]>
      </description>
      <content:encoded>
        <![CDATA[<p><img src="https://s3.amazonaws.com/nandovieira/media/nv-logo.png" alt="Nando Vieira" class="align-right transparent" /></p>

<p>Depois de quase 9 anos, chegou a hora de dizer adeus ao Simples Ideias. Mas não se preocupe. O conteúdo continua o mesmo, apenas com um domínio diferente.</p>

<p>Há pouco mais de um ano comecei a escrever em inglês em <a href="http://nandovieira.com">http://nandovieira.com</a>. Não demorou muito para que eu começasse a achar estranho ter dois domínios extremamente diferentes para conteúdos tão semelhantes. Então, como eu já tinha o domínio <a href="http://nandovieira.com.br">http://nandovieira.com.br</a> disponível, foi só uma questão de preparar a migração.</p>

<h2>Como fazer a migração dos domínios</h2>

<p>O primeiro passo foi garantir que todas as URLs estavam sendo redirecionadas corretamente com o <a href="https://en.wikipedia.org/wiki/HTTP_301">status 301</a>. Isso foi bem fácil de fazer com NGINX.</p>
<div class="highlight"><pre class="highlight nginx"><code><span class="k">server</span> <span class="p">{</span>
  <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
  <span class="kn">server_name</span> <span class="s">www.simplesideias.com.br</span><span class="p">;</span>
  <span class="kn">return</span> <span class="mi">301</span> <span class="s">http://nandovieira.com.br</span><span class="nv">$request_uri</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">server</span> <span class="p">{</span>
  <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
  <span class="kn">server_name</span> <span class="s">simplesideias.com.br</span><span class="p">;</span>
  <span class="kn">return</span> <span class="mi">301</span> <span class="s">http://nandovieira.com.br</span><span class="nv">$request_uri</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>Depois, fiz a migração dos comentários no <a href="http://disqus.com">Disqus</a>. Como os redirecionamentos estavam corretos, utilizei a opção <a href="https://help.disqus.com/customer/portal/articles/912834-redirect-crawler">Redirect Crawler</a>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/disqus-migration-tool.png" alt="Disqus Migration Tool"></p>

<p>Finalmente, tive que fazer a migração da busca no <a href="https://www.google.com/webmasters/tools/">Google Webmasters Tool</a>. Após criar uma nova propriedade para o domínio <code>nandovieira.com.br</code>, foi necessário solicitar a <em>mudança de endereço</em>. No domínio original, escolha a opção <em>Mudança de Endereço</em>.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/google-webmasters-change-address-1.png" alt="Google Webmasters Tool - Mudar endereço"></p>

<p>Depois, basta seguir cada um dos passos apresentados, para solicitar a migração. No meu caso, este processo ainda está em andamento.</p>

<p><img src="https://s3.amazonaws.com/nandovieira/media/google-webmasters-change-address-2.png" alt="Google Webmasters Tool - Mudar endereço"></p>

<p>Uma coisa interessante é que mesmo esse processo não tendo sido finalizado, não senti nenhum impacto no número de visitas, segundo o Google Analytics.</p>

<h2>Finalizando</h2>

<p>Era isso! Nenhuma alteração significativa para quem já acompanha o blog. O link do feed também está sendo redirecionado, então tudo deve continuar funcionando sem que você precise alterar nada.</p>

<p>Ah, nesse processo aproveitei e dei uma renovada no visual. Nada radical, mas achei que deu uma modernizada boa.</p>
]]>
      </content:encoded>
      <link>https://nandovieira.com.br/adeus-simples-ideias</link>
      <guid>https://nandovieira.com.br/adeus-simples-ideias</guid>
      <pubDate>Fri, 16 Oct 2015 13:35:00 -0300</pubDate>
    </item>
  </channel>
</rss>
