Pipeline para Iniciantes

Este é provavelmente um dos assuntos mais confusos para quem está iniciando com Ruby on Rails. Antigamente, as regras eram simples:

  • coloque todos os seus assets (imagens, stylesheets e javascripts) organizados nas pastas public/images, public/stylesheets, public/javascripts;
  • utilize helpers como image_tag, stylesheet_link_tag e javascript_include_tag;
  • configure seu servidor web (Apache, NGINX) para servir URIs como /images/rails.png diretamente de public/images/rails.png para não precisar passar pelo Rails;
Pronto, está tudo preparado para funcionar. Porém, existiam e ainda existem muitas situações que essa regra não cobria e diversas técnicas, “boas práticas” e gems externas precisaram ser criadas para resolvê-las. Em particular, temos as seguintes situações cotidianas em desenvolvimento web:
  • quando se tem muitos assets, como javascripts, é considerado boa prática “minificá-los”, ou seja, otimizar ao máximo a quantidade de bytes eliminando supérfluos como espaços em branco e quebras de linha, nomes de variáveis e funções longas, etc. E além disso concatenar a maior quantidade de arquivos num único quanto possível. Em desenvolvimento, precisamos ter todos abertos e individuais para facilitar o debugging, mas em produção o correto é “compilá-los”
  • cache precisa ser usado o máximo possível e escrever o caminho a um path manualmente, como <img src=“/images/rails.png”/> é ruim, pois se precisarmos mudar o conteúdo dessa imagem, os usuários precisariam limpar seus caches pois o correto é configurarmos os servidores web com diretivas para manter assets no cache local por um longo período de tempo (1 ano ou mais). Helpers como image_tag criavam caminhos como <img src=“/images/rails.png?12345678”/>, sendo esse número derivado do timestamp de modificação do asset. Assim, se o asset era atualizado esse número mudava. Mas isso não funciona bem com muitos tipos de caches e proxies, que ignoram o que vem depois do “?”
  • quando uma página possui dezenas ou às vezes centenas de pequenas imagens e ícones (setas, botões, logotipos de seção, linhas, bordas, etc), o correto é usar a mesma técnica que usamos com stylesheets e javascripts: concatenar muitas imagens em um único arquivo maior e então utilizar CSS para manipular a posição x e y dentro dessa única imagem grande para posicioná-la corretamente onde precisamos.
  • começamos a utilizar vários tipos diferentes de geradores de templates, como LESS e SASS para gerar stylesheets, CoffeeScript para gerar Javascript, além do próprio ERB para adicionar conteúdo dinâmico nos templates.
Para resolver essas e outras situações é que foi criado o chamado Asset Pipeline, que é um conjunto de bibliotecas e convenções para resolver o problema de assets da melhor forma possível. O Asset Pipeline sozinho não resolve tudo, ele é um framework para que seja possível integrar diferentes soluções de forma organizada.
Tudo que será explicado neste artigo vale para o Rails 3.2 e superior, existem diferenças importantes nas versões anteriores que não serão tratadas aqui. Leia o Rails Guides, especialmente os Release Notes de cada versão.

Iniciando um projeto Rails

Quando iniciamos um novo projeto com o comando rails new novo_projeto, o primeiro arquivo que você vai querer mexer é o Gemfile:
# Original
group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'

  # See https://github.com/sstephenson/execjs#readme for more supported runtimes
  # gem 'therubyracer', :platforms => :ruby

  gem 'uglifier', '>= 1.0.3'
end

# Recomendado para iniciar
group :assets do
  gem 'sass-rails'
  gem 'compass-rails'

  # See https://github.com/sstephenson/execjs#readme for more supported runtimes
  gem 'therubyracer', :platforms => :ruby

  gem 'uglifier'
end
Não vamos entrar na controvérsia agora sobre CoffeeScript. Se você está iniciando, esqueça CoffeeScript por enquanto para não complicar ainda mais. Por outro lado, usar o Compass e particularmente o Compass Rails é algo que nem precisamos discutir já que o Compass provê diversos mixins de Sass muito úteis.
Aliás, se você ainda não conhece SASS, faça a você mesmo um favor e aprenda. Se você entende CSS, não vai ter problemas entendendo Sass, em particular a versão “SCSS” ou “Sassy CSS” que não é mais do que um conjunto acima do CSS3. Lembrando que mesmo escolhendo usar SASS podemos misturar arquivos .css.scss e arquivos convencionais .css no mesmo projeto.
Ao modificar o arquivo Gemfile, lembre-se de executar os seguintes comandos no terminal:
bundle
Para exercitar, vamos criar um simples controller com uma única página dinâmica para entender o que podemos fazer com isso. De volta ao terminal faça o seguinte:
rm public/index.html
bundle exec rails g controller home index
O resultado será:
  create  app/controllers/home_controller.rb
   route  get "home/index"
  invoke  erb
  create    app/views/home
  create    app/views/home/index.html.erb
  invoke  test_unit
  create    test/functional/home_controller_test.rb
  invoke  helper
  create    app/helpers/home_helper.rb
  invoke    test_unit
  create      test/unit/helpers/home_helper_test.rb
  invoke  assets
  invoke    js
  create      app/uploads/javascripts/home.js
  invoke    scss
  create      app/uploads/stylesheets/home.css.scss
E para combinar, já que estamos recomendando SCSS, vamos apagar o arquivo app/stylesheets/application.css e criar um novo:
rm app/stylesheets/application.css
touch app/stylesheets/application.css.scss
E nesse novo arquivo podemos colocar somente:
@import "compass"
Outra boa prática é ignorar o diretório public/uploads do repositório Git (você utilizar Git, correto?). Faça o seguinte:
echo "public/uploads" >> .gitignore
E agora já podemos iniciar o servidor local de Rails e examinar o que temos até agora:
bundle exec rails s

Processo de Pré-Compilação

Resumidamente, em termos de assets temos os seguintes principais elementos e estrutura:
app
  assets
    images
      rails.png
    javascripts
      application.js
      home.js
    stylesheets
      application.css.scss
      home.css.scss
  views
    home
      index.html.erb
    layouts
      application.html.erb
config
  application.rb
public
  assets
Gemfile
Gemfile.lock
O código fonte do layout app/views/layouts/application.html.erb contém o seguinte:
<!DOCTYPE html>
<html>
<head>
  <title>NovoProjeto</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>
Se você já tinha visto até o Rails 2.x, um layout padrão ERB não é tão diferente. Com o servidor de pé, em ambiente de desenvolvimento, vejamos o HTML gerado ao abrir http://localhost:3000/home/index:
<!DOCTYPE html>
<html>
<head>
  <title>NovoProjeto</title>
  <link href="/uploads/application.css" media="all" rel="stylesheet" type="text/css" />
  <script src="/uploads/jquery.js?body=1" type="text/javascript"></script>
  <script src="/uploads/jquery_ujs.js?body=1" type="text/javascript"></script>
  <script src="/uploads/home.js?body=1" type="text/javascript"></script>
  <script src="/uploads/application.js?body=1" type="text/javascript"></script>
  <meta content="authenticity_token" name="csrf-param" />
  <meta content="OFmZwwtshevVgcs1DUg56WVIQ8NcJZsri/nUubhEJCk=" name="csrf-token" />
</head>
<body>

<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

</body>
</html>
No HTML gerado, note que os links para os assets apontam todos para /uploads. Além disso note que a chamada javascript_include_tag(“application”) expandiu para 4 javascripts diferentes. Para entender isso, precisamos examinar mais de perto o arquivo app/uploads/javascripts/application.js:
...
//= require jquery
//= require jquery_ujs
//= require_tree .
Sobre o detalhe do jQuery, todo novo projeto Rails tem declarado gem ‘jquery-rails’ no Gemfile.
Os arquivos application.∗ podendo “∗” ser “js”, “js.coffee”, “css”, “css.scss”, “css.sass”, “js.erb”, “css.erb”, etc. Eles são conhecidos como “Manifestos”. São arquivos “guarda-chuva” que declaram todos os outros arquivos que eles dependem, em ordem, para serem concatenados em um único arquivo ao serem compilados.
No exemplo padrão, no application.js a primeira e segunda linha com require declaram o jquery.js e depois o jquery_ujs.js e a terceira linha com require_tree . manda carregar todos os outros arquivos javascripts no mesmo diretório que, por acaso, tem o home.js criado pelo gerador de controller que usamos antes. Agora vejam novamente o HTML gerado e verá que são exatamente os javascripts carregados na ordem que expliquei, sendo o quarto o próprio conteúdo do arquivo application.js.
Normalmente usar o require_tree . não é exatamente ruim se os javascripts não dependem da ordem de carregamento, mas você provavelmente vai querer declarar explicitamente coisas como plugins de jQuery para garantir que eles estão carregados antes de poder usá-los.
Para explicar como tudo isso funciona é importante pararmos o servidor Rails que subimos antes e reexecutá-lo em modo produçao:
bundle exec rails s -e production
Agora, se tentarmos carregar a mesma URL http://localhost:3000/home/index no browser, receberemos um erro 500 com o seguinte backtrace:
Started GET "/home/index" for 127.0.0.1 at 2012-07-01 03:31:55 -0300
Connecting to database specified by database.yml
Processing by HomeController#index as HTML
  Rendered home/index.html.erb within layouts/application (12.0ms)
Completed 500 Internal Server Error in 155ms

ActionView::Template::Error (application.css isn't precompiled):
    2: <html>
    3: <head>
    4:   <title>NovoProjeto</title>
    5:   <%= stylesheet_link_tag    "application", :media => "all" %>
    6:   <%= javascript_include_tag "application" %>
    7:   <%= csrf_meta_tags %>
    8: </head>
  app/views/layouts/application.html.erb:5:in `_app_views_layouts_application_html_erb__408740569075721590_70099961775620'
Este é o sinal que não realizamos um passo importante que deve ser executado toda vez que você realizar uma atualização em produção: pré-compilar os assets. É o processo que lê os arquivos manifesto e realiza a concatenação dos arquivos declarados e sua minificação (utilizando a gem Uglifier). Portanto, precisamos executar o seguinte:
bundle exec rake assets:precompile
Lembrando que antes disso o diretório public/uploads estava originalmente vazio (e em desenvolvimento, você deve garantir que esse diretório esteja sempre vazio, já explicamos porque). Após executar a a pré-compilação, esse diretório terá os seguintes arquivos:
application-363316399c9b02b9eb98cd1b13517abd.js
application-363316399c9b02b9eb98cd1b13517abd.js.gz
application-7270767b2a9e9fff880aa5de378ca791.css
application-7270767b2a9e9fff880aa5de378ca791.css.gz
application.css
application.css.gz
application.js
application.js.gz
manifest.yml
rails-be8732dac73d845ac5b142c8fb5f9fb0.png
rails.png
E para entender vejamos o código-fonte do HTML gerado em produção:
<!DOCTYPE html>
<html>
<head>
  <title>NovoProjeto</title>
  <link href="/uploads/application-7270767b2a9e9fff880aa5de378ca791.css" media="all" rel="stylesheet" type="text/css" />
  <script src="/uploads/application-363316399c9b02b9eb98cd1b13517abd.js" type="text/javascript"></script>
  <meta content="authenticity_token" name="csrf-param" />
<meta content="OFmZwwtshevVgcs1DUg56WVIQ8NcJZsri/nUubhEJCk=" name="csrf-token" />
</head>
<body>

<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

</body>
</html>
Compare este HTML com o anterior que analisamos gerado em ambiente de desenvolvimento. Em vez de 1 arquivo CSS e 4 Javascripts, temos apenas 1 CSS e 1 Javascript.
Para entendermos melhor, vejamos o que tem no arquivo public/uploads/manifest.yml:
rails.png: rails-be8732dac73d845ac5b142c8fb5f9fb0.png
application.js: application-363316399c9b02b9eb98cd1b13517abd.js
application.css: application-7270767b2a9e9fff880aa5de378ca791.css
Ou seja, o arquivo application.js é idêntico ao application-363316399c9b02b9eb98cd1b13517abd.js. Se algum dos arquivos declarados no manifesto app/uploads/javascripts/application.js mudar, esse número sufixo irá mudar e o HTML apontará para o novo. Olhando novamente nossa lista de situações que precisam ser solucionadas, que apresentamos no início do arquivo, temos já até aqui a solução de 3 dos pontos:
  • o ponto 1 explica o problema que é sempre melhor ter apenas um único arquivo de CSS ou JS do que dezenas deles separados, pois o navegador só precisa ter o peso de pedir um único arquivo (quanto mais arquivos, independente do tamanho, mais tempo vai demorar para a página renderizar). Além disso, graças ao Uglifier teremos esses arquivos “minificados”, ou seja, reescritos de forma a minimizar seu tamanho em bytes sem modificar a lógica da programação. Além disso, o pipeline vai um passo além e tem as versões de todos esses arquivos com extensão “.gz” que significa “gzip”. Se o browser fizer uma requisição dizendo que aceita conteúdo compactado em formato zip, se o web server disser que entende zip, ele pode diretamente enviar a versão do arquivo com extensão “.gz”. No exemplo acima, isso significa enviar um JS de 34kb em vez dos 98kb descomprimidos.
  • o ponto 2 explica o problema de quanto assets mudam mas o browser guarda em cache baseado na URL que ele carregou. Se ele pedisse http://localhost:3000/uploads/application.js, mesmo se o JS fosse modificado, o browser não pediria novamente porque a boa prática diz que o web server deveria enviar cabeçalho dizendo para esse tipo de arquivo ficar em cache por 1 ano. Mas como o HTML na realidade pede por http://localhost:3000/uploads/application-363316399c9b02b9eb98cd1b13517abd.js, e se o JS mudar, esse número vai mudar também, não importa mais que esses assets fiquem indefinidamente em cache, pois da próxima vez que precisar dar versão mais nova, o nome do arquivo será completamente diferente do que estava no cache.
  • finalmente, o ponto 4 explica sobre os diferente geradores de assets. Implicitamente já podemos ver isso no caso do SASS, onde arquivos com extensão “.css.scss” são convertidos em “.css”.
Para reforçar o ponto 2, vamos adicionar a seguinte função no arquivo app/uploads/javascripts/application.js:
function helloWorld() {
  console.log("Hello World");
}
Agora executamos a pré-compilação novamente:
bundle exec rake assets:precompile
O que temos no diretório public/uploads será:
application-363316399c9b02b9eb98cd1b13517abd.js
application-363316399c9b02b9eb98cd1b13517abd.js.gz
application-4fee97e9e402a9816ab9b3edf7a4c08b.js
application-4fee97e9e402a9816ab9b3edf7a4c08b.js.gz
application-7270767b2a9e9fff880aa5de378ca791.css
application-7270767b2a9e9fff880aa5de378ca791.css.gz
application.css
application.css.gz
application.js
application.js.gz
manifest.yml
rails-be8732dac73d845ac5b142c8fb5f9fb0.png
rails.png
Como não limpamos o diretório antes, temos a versão antiga e a recente. Compare, a anterior se chamava application-363316399c9b02b9eb98cd1b13517abd.js e a nova com a função de demonstração se chama application-4fee97e9e402a9816ab9b3edf7a4c08b.js. Reiniciando o servidor Rails no ambiente de produção e vendo o novo HTML gerado, verá este trecho:
<script src="/uploads/application-4fee97e9e402a9816ab9b3edf7a4c08b.js" type="text/javascript"></script>
Espero que esse detalhamente deixe bem claro o objetivo do pipeline de pré-compilação e as convenções de nomenclatura e quais problemas ele soluciona. Num artigo que escrevi recentemente chamado Enciclopédia do Heroku explico como mover esses assets pré-compilados para uma conta na Amazon S3, com o objetivo de descarregar processamento do seu servidor de aplicação, servindo a partir de um CDN, o que deve melhorar a experiência do usuário final ao carregar assets de um servidor mais próximo.

Ambientes de Desenvolvimento e Produção

Agora entenda uma regra básica:
  • toda URL que você passar ao Rails é analisada pelo sistema de roteamento, que você configura em config/routes.rb
  • a exceção é tudo que estiver no diretório public, graças ao middleware Rack::Static.
Ou seja, se você pedir por http://localhost:3000/uploads/application.js isso nem vai passar pelo Rails porque, como vimos na listagem do diretório public/uploads acima, esse arquivo existe lá e, portanto, será servido diretamente.
Por isso a recomendação, depois do último exercício com pré-compilação é apagar tudo:
rm -Rf public/uploads
Desta forma, quando você subir o servidor Rails no ambiente padrão de desenvolvimento, a mesma URL irá passar pelo Rails, pelo Asset Pipeline, e processará o arquivo app/uploads/javascripts/application.js. Assim, toda vez que fizer modificações e recarregar a página no navegador, você verá as mudanças imediatamente. Se estivesse précompilado, veria a versão antiga, sem as modificações novas.
A compilação em tempo real só acontece em desenvolvimento pois, obviamente, é um processo lento. Em produção, ninguém em sã consciência vai alterar nenhum arquivo direto em servidor de produção, portanto é importante ter tudo précompilado em public/uploads para economizar o aplicativo Rails do trabalho.
Outra coisa importante, os arquivos de assets que se chamam “application”, são automaticamente compilados. Mas digamos que você tem um stylesheet que não vai carregar no layout principal, mas só em algumas outra seções, digamos que se chame special.css, ou seja, precisamos encontrá-lo em http://localhost:3000/uploads/special.css, criamos nesta estrutura:
app
  assets
    stylesheets
      special.css
Porém, se reexecutarmos o rake task assets:precompile novamente, verá que esse arquivo não aparece em public/uploads. Recapitulando, arquivos “application” são automaticamente usados na précompilação. Todo asset declarado nesses manifestos, será usado dentro dos arquivos minificados “application”. Agora, para adicionar arquivos extras, precisamos declará-los manualmente em config/application.rb, adicionado uma linha assim:
config.assets.precompile += %w(special.css)
Colocar tudo no “application” e carregar esse arquivo no layout principal para que toda página tenha tudo? Ou separar CSS e JSS que é necessário seção a seção do site? Não tenho uma regra rígida. Se a aplicação não for tão grande assim, provavelmente centralizar tudo em “application” é a saída mais simples de gerenciar. Porém se a aplicação for ou muito grande, ou se determinada seção é relativamente mais pesada em JS que as demais, por exemplo, talvez valha a pena gerenciar assets em separado, usando a configuração acima.
Agora sim, se executar a task de precompile novamente, teremos algo como:
special-f7bf76f875ce5c9edd0075eaea3f6140.css
special-f7bf76f875ce5c9edd0075eaea3f6140.css.gz
special.css
special.css.gz

Compass e SASS

Agora, por que adicionamos o Compass? Não vou fazer desta seção um tutorial de Compass, mas para que entendem o básico, podemos adicionar o seguinte CSS de exemplo em app/uploads/stylesheets/application.css.scss:
@import "compass";

.box {
  font {
    family: Lucida Grande;
    size: 12px;
  }
  width: 400px;
  padding: 15px;
  border: 1px solid black;
  @include text-shadow(1px 0 1px opacify(#5c5c5c, .37));
  @include box-orient(horizontal);
  @include border-radius(20px, 20px);
}
Na maior parte parece um CSS normal, mas a diferença maior está nos comandos @include do Sass, que serve para carregar “Mixins”. Pense em Mixin no Sass como “funções” que retornam CSS parametrizável e reusável. O Compass é uma biblioteca de Mixins que encapsulam alguns dos aspectos mais comuns de CSS. No exemplo, estamos chamando os mixins text-shadow e border-radius, cuja função deve ser bastante óbvia. Vamos ver o CSS que isso gera:
/* line 3, ../../app/uploads/stylesheets/application.css.scss */
.box {
  width: 400px;
  padding: 15px;
  border: 1px solid black;
  text-shadow: 1px 0 1px #5c5c5c;
  -webkit-box-orient: horizontal;
  -moz-box-orient: horizontal;
  -ms-box-orient: horizontal;
  box-orient: horizontal;
  -webkit-border-radius: 20px 20px;
  -moz-border-radius: 20px / 20px;
  -ms-border-radius: 20px / 20px;
  -o-border-radius: 20px / 20px;
  border-radius: 20px / 20px;
}
/* line 4, ../../app/uploads/stylesheets/application.css.scss */
.box font {
  family: Lucida Grande;
  size: 12px;
}
Fica claro a função de “nesting”, para herdar estilos de um elemento pai, que usamos no elemento “font” para não repetir em blocos separados, como no CSS final. O “text-shadow” foi simples, mas o “box-orient” e border-radius" automaticamente adicionaram todas as diretivas específicas de cada browser, coisas como “-webkit”, “-moz”, etc.
Veja a documentação de referência do Compass para encontrar todos os mixins e exemplos detalhados de como usá-los.
Isso é interessante, mas a principal função, para fechar todas as situações que descrevi na introdução do artigo, é a que se segue.
Primeiro, vamos adicionar duas novas imagens na pasta app/images/social-icons:
app
  images
    social-icons
      facebook.png
      linkedin.png
      twitter.png
Agora, vamos adicionar o seguinte HTML em app/views/home/index.html.erb:
...
<ol class="social">
  <li class="twitter"><a href="http://www.twitter.com">Twitter</a></li>
  <li class="facebook"><a href="http://www.facebook.com">Facebook</a></li>
  <li class="linkedin"><a href="http://www.linkedin.com">LinkedIn</a></li>
</ol>
Nada demais, apenas o objetivo de adicionar ícones de redes sociais com links a eles. Para estilizá-los, vamos completar nosso SCSS assim:
@import "compass";
@import "social-icons/*.png";

...

ol.social {
  @include horizontal-list;
  @each $network in twitter, facebook, linkedin {
    li.#{$network} a {
      @include social-icons_sprite(#{$network})
    }
  }
  a {
    height: 32px;
    width: 32px;
    display: block;
    text-indent: -9000px;
    color: #FFF;
  }
}
Novamente, estude SASS para entender essa sintaxe e também note que novamente usamos um mixin do Compass chamado horizontal-list. Lembre que colocamos 3 novas imagens, listados acima. Agora neste SCSS, na segunda linha, fazemos um @import de todos esses ícones.
Agora localize esta linha: @include social-icons_sprite(#{$network}). O nome da pasta, com o sufixo “_sprite” se torna um mixin, que recebe como parâmetro o nome da imagem/sprite. O que significa isso no CSS gerado ao final? Vejamos:
/* line 58, social-icons/*.png */
.social-icons-sprite, ol.social li.twitter a, ol.social li.facebook a, ol.social li.linkedin a {
  background: url(/uploads/social-icons-s25bc94da3e.png) no-repeat;
}
...
/* line 20, ../../app/uploads/stylesheets/application.css.scss */
ol.social li.twitter a {
  background-position: 0 -32px;
}
/* line 20, ../../app/uploads/stylesheets/application.css.scss */
ol.social li.facebook a {
  background-position: 0 -64px;
}
/* line 20, ../../app/uploads/stylesheets/application.css.scss */
ol.social li.linkedin a {
  background-position: 0 0;
}
...
Aqui vemos o que aconteceu: as 3 imagens separadas foram concatenadas numa única, declarada no topo do CSS, chamada neste exemplo de /uploads/social-icons-s25bc94da3e.png. Agora, cada uma das classes de rede social que criamos tem um reposicionamento da mesma imagem, como: background-position: 0 -64px;.
Isto resolve o ponto 2 da introdução do artigo, que estava pendente: gerenciamento de sprites. Dependendo da quantidade de pequenas imagens que uma única página do seu site tem, somente este truque pode causar um impacto positivo que seus usuários irão notar rapidamente por causa do aumento na velocidade de renderização.
Para complementar, sempre que usarmos chamadas como background: url(/uploads/social-icons-s25bc94da3e.png) no CSS, nunca devemos digitar a URL absoluta manualmente. Devemos deixar o Asset Pipeline cuidar disso, como ele fez aqui no caso dos sprites.
Vejamos um exemplo mais concreto. Vamos adicionar o seguinte ao nosso application.css.scss:
h1 {
  padding-left: 60px;
  height: 70px;
  background: url("/uploads/rails.png") no-repeat;
}
O que confunde é que isso de fato funciona. Porém, caímos nos problema mencionados no início do artigo: sem o número timestamp, se atualizarmos a imagem, os usuários ficarão travados na versão antiga em cache local. Se quisermos migrar para CDN vamos ter problemas de mudar todas essas URLs manualmente, etc. Portanto, no caso de SASS o correto é usar a função image-url e fazer desta forma:
  background: image-url("rails.png") no-repeat;
Isto irá gerar a URL correta. Temos as seguinte variações:
image-url("rails.png")         # url(/uploads/rails.png)
image-path("rails.png")        # "/uploads/rails.png".
asset-url("rails.png", image)  # url(/uploads/rails.png)
asset-path("rails.png", image) # "/uploads/rails.png"
Agora, se for necessário URLs de assets dentro do Javascript, não há equivalente no application.js puro, por isso precisaríamos renomeá-lo para application.js.erb e então a mesma regra que usaríamos em views HTML ERB normais valem:
var imagem     = "<%= image_path("rails.png") %>";
var tag_imagem = "<%= image_tag("rails.png") %>";
var audio      = "<%= audio_path("rails.mp3") %>";
var tag_audio  = "<%= image_tag("rails.mp3") %>";
var video      = "<%= video_path("rails.m4v") %>";
var tag_video  = "<%= image_tag("rails.m4v") %>";
Vejam a documentação do ActionView::Helpers::AssetTagHelper para entender melhor sobre estes helpers, mas o importante é: se estiver escrevendo a URL de um asset manualmente, como um string, você está fazendo errado.

NGINX

Não vou entrar em detalhes sobre como configurar um NGINX completo, mas fica um lembrete para não esquecer de adicionar à configuração do servidor o seguinte trecho:
location ~ ^/uploads/ {
  expires 1y;
  add_header Cache-Control public;
  add_header Last-Modified "";
  add_header ETag "";
  break;
}
Isso garantirá que o browser do usuário guarde todos os assets no seu cache local para não pedir novamente. Com o Asset Pipeline, como explicamos exaustivamente acima, assets atualizados são imunes ao cache local.
E para diminuir ainda mais a quantidade de bits transportado entre o servidor e o browser dos usuários, lembre de checar se o suporte a gzip está ativado:
##
# Gzip Settings
##

gzip on;
gzip_disable "msie6";

# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

Conclusão

Como podem ver, o Asset Pipeline é bastante simples para a grande maioria dos casos de uso. As regras são diferentes mas simples:
  • Assets devem ir todas em app/uploads
  • Em desenvolvimento public/uploads deve ficar vazio. Em produção, sempre execute rake assets:precompile
  • Não digite URL de assets manualmente, use os helpers de URL
Isso lhe dará, “de graça”:
  • concatenação de arquivos de assets, minificação de javascript e stylesheets
  • compatibilidade com caches, CDNs e suporte a gzip
  • gerenciamento de sprites numa única imagem com posicionamento via CSS
  • suporte a diferentes geradores de templates, como SASS
Expanda seus conhecimentos, aprenda mais sobre:
Esqueçam rumores, opiniões contrárias, rants e trolls. O Asset Pipeline certamente não é simples. Porém está longe do bicho de sete cabeças que costumam descrever. No início haviam muitos bugs, que já foram corrigidos e, toda vez que alguém falar em “Asset Poopline”, normalmente é problema de de BIOS mais do que de ferramenta. Google e Stackoverflow, for the help.

Comentários

Postagens mais visitadas deste blog

Rails CanCan

Meus insights mais valiosos sobre criptomoedas para 2018 e além

Como pegar a senha do Whatsapp de um Android ou Iphone