Essa publicação é compatível com Rails 5. Não utilizamos a importação de JS via webpacker. Em breve farei uma publicação de como importar JS usando Rails 6 e webpacker

Antes de começar

Antes de começar o tutorial, gostaria de explicar duas coisas antes

  1. Onde podemos colocar nossos assets no Ruby on Rails: Veja aqui
  2. Como funciona os layouts no Ruby on Rails: Veja aqui

Caso você já saiba como funciona, pode pular direto para o tutorial: Veja aqui

Importando CSS e JS no Rails

Primeiro vamos começar com alguns conceitos. No Rails podemos inserir nossos assets CSS e JS em três locais:

app/assets    -> Para assets criado pelo próprio Rails
lib/assets    -> Para assets que você mesmo criou
vendor/assets -> Para assets de terceiros

E como pode ser visto acimo, escolhemos o onde por o CSS ou JS de acordo com a origem dele, fazemos isso para termos um código organizado.

app/assets
     |------- stylesheets
     |------- javascripts
lib/assets
     |------- stylesheets
     |------- javascripts
vendor/assets
     |------- stylesheets
     |------- javascripts

É importante que o nome das pastas stylesheets e javascripts estejam no plural, pois é assim que o Rails vai buscar quando usamos alguma tag de assets, Exemplo:

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

<%= stylesheet_link_tag 'users', media: 'all', 'data-turbolinks-track': 'reload' %>
                          # Em app/assets/stylesheets/users.css ou 
                          # Em lib/assets/stylesheets/users.css ou 
                          # Em vendor/assets/stylesheets/users.css

Então, toda vez que vamos referenciar estes arquivos usando código Rails, usaremos um path relativo começando de stylesheets se for um CSS ou javascripts se for um JS. Ao fazer um import com alguma das tags acima, o Rails irá procurar o arquivo informado em todas essas pastas, caso não encontre, um erro acontece (ver foto abaixo).

Assets not precompiled

O que tem de mais importante nesse erro é a seguinte informação:

Sprockets:: Rails:: Helper:: AssetNotPrecompiled in Users#index

E para resolver isso, basta informar no config/initializers/assets.rb que tal CSS ou JS precisa ser precompilado. Como falo logo abaixo.

Rails.application.config.assets.precompile += %w( users.css )

Caso seja necessário adicionar mais de uma arquivo para ser compilado, o arquivo assets.rb deve ficar mais ou menos assim:

Rails.application.config.assets.precompile += %w( users.css
                                                  users.js
                                                  admin.css
                                                  outro_javascript.js
                                                  outros_estilos.css
                                                  ...
                                                )

Obs 1: qualquer mudança nesse arquivo é necessário o reinicio do rails server

Obs 2: os arquivos application.css e application.js não precisam ser informados em assets.rb. O Rails já sabe que esses arquivos devem ser compilados.

Funcionamento dos layouts no Ruby on Rails

Para facilitar o entendimento iremos supor um cenário:

Imagine que você tenha um sistema que possua dois modelos (usuários e administradores) e que você deseja que para cada modelo seja apresentado um layout diferente, por exemplo, para o usuário, você deseja que o layout seja no estilo de um blog. Já para os administradores, você deseja que seu sistema tenha uma aparência de dashboard.

Executando nosso cenário acima, iremos começar com o layout do usuário. O primeiro passo seria criar um arquivo chamado users.css em app/assets/stylesheets/

Dentro de users.css podemos fazer duas coisas:

  1. Importar outros arquivos CSS (nossos ou de terceiros, como falado acima)
  2. Escrever nossas próprias regras de estilização
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
 *= require sb-admin-2
 *= require_tree .
 *= require_self
 */

Relembrando 1: o require sb-admin-2 faz importação de um arquivo CSS de terceiro. Tal arquivo está salvo em vendor/assets/stylesheets

Relembrando 2: o require_tree . faz a importação de todos os arquivos CSS dentro da pasta app/assets/stylesheets

Relembrando 3: o require_self define a posição em que os estilos escritos no arquivo users.css, ou seja, ao se compilado, o arquivo CSS vai ser estruturado da seguinte maneira: primeiro virá os estilos advindos de sb-admin-2, posteriormente vem os estilos de outros arquivos na pasta app/assets/stylesheets e por fim vem os estilos do próprio arquivo. E por que isso é importante? Porque no CSS se temos dois seletores repetidos, o que prevalecerá será o que foi definido por último.

Com os estilos definidos, iremos para app/views/layouts e criaremos um novo arquivo lá, como esse arquivo é referente a usuário, iremos chamar de users.html.erb, nele conterá as partes que se repetem no nosso layout, por exemplo, a navegação e o footer do site costuma se manter o mesmo para qualquer página acessada, então esses componentes vão no layout.

O nosso arquivo de layout de usuários deve ter o seguinte (por enquanto):

# app/views/layouts/users.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
  
    <%= stylesheet_link_tag 'users', media: 'all', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

Do snippet acima, podemos reforçar duas coisas:

  1. A tag stylesheet_link_tag faz todo trabalho para gente importando o arquivo CSS, quando a página HTML é renderizada, essa tag se transforma em um <link rel="stylesheet" href="nome_do_arquivo"/>

  2. A tag <%= yield %> é responsável por adicionar o restante do HTML da página, o conteúdo adicionado aí vem da view (app/view/users/) referente a rota que estamos acessando.

Agora precisamos informar ao controller de usuários que iremos utilizar o layout users.html.erb, se não fizermos isso, ele continuará a utilizar o layout application.html.erb, que é o padrão do Rails. Para resolver isso basta adicionar 1 linha de código em nosso controller:

# app/controllers/users_controller.rb

class UsersController < ApplicationController
  layout 'users'
  
  # GET /users
  def index; end
end

Para finalizar precisamos informar ao Rails que ele deve compilar o arquivo users.css quando ele for iniciar o server (falei isso um pouco acima:Veja aqui)

# config/initializers/assets.rb

Rails.application.config.assets.precompile += %w( users.css )

Agora basta reiniciar o server e podemos ver nosso estilo aplicado para o modelo de usuários.

No nosso cenário tratamos de usuário e administrados, o processo para o segundo é o mesmo que foi descrito acima. Vou listar aqui abaixo e peço para você fazer com o objetivo de fixar o que foi aprendido:

  1. Criar o arquivo admins.css em app/assets/stylesheets/
  2. Importar outros arquivos CSS (seus ou de terceiros) e escrever seu próprio estilo
  3. Criar o arquivo admins.html.erb em app/views/layouts
  4. Importar o arquivo admins.css dentro de admins.html.erb usando a tag stylesheet_link_tag
  5. Informar ao controller de administradore que ele deve utilizar o layout admins.html.erb
  6. Informar ao Rails que ele deve pre-compilar o arquivo admns.css
  7. Reiniciar o servidor e correr para o abraço, quer dizer, verificar que tudo ocorreu bem.

Pronto, como os conceitos iniciais explicados, podemos começar a adicionar novos imports. Irei continuar usando nosso cenário de usuários e administradores, mas agora iremos ver um caso real de aplicação. Usarei layout Sb Admin como exemplo. Aqui vai um resumo do que iremos fazer:

  1. Criaremos um aplicação Rails
  2. Baixaremos o template nesse link: Sb Admin
  3. Criaremos o model e o controller para usuários
  4. Criaremos o arquivo users.css em app/assets/stylesheets/
  5. Importaremos os arquivos de estilo em users.css
  6. Separemos os layouts em partials (você irá entender quando chegarmos lá)
  7. Criaremos o arquivos users.js em app/assets/javascripts/
  8. Importaremos os arquivos javascripts em users.js
  9. Criaremos o arquivos users.html.erb em app/views/layouts/
  10. Importaremos os arquivos users.css e users.js no layout users.html.erb
  11. Adicionaremos os arquivos users.css e users.js em config/initializers/assets.rb

Mãos à obra

  • Criando uma aplicação rails (utilizamos a versão 5.2.4)
    rails _5.2.4_ new app -d postgresql
    
  • Criando o modelo de usuários
    rails g model user name:string last_name:string email:string password:string
    

Após criado o modelo, iremos fazer algumas validações. Gosto de fazer as validações antes de rodar a migration, pois assim não me esqueço de realizá-la:

# app/models/user.rb

class User < ApplicationRecord
  validates :name, presence: true
  validates :password, presence: true
  validates :email, presence: true, uniqueness: true
end

No código acima, validamos que os campos name, password e email devem ser obrigatórios e validamos também que o email deve ser único, ou seja, se alguém cadastrar esse email ele não poderá ser usado para outro usuário.

Agora podemos rodar a migration:

rails db:migrate

Agora é hora de criar o controller para usuários e gerar suas rotas (seguinte as boas práticas, iremos utilizar o controller com o nome no plural):

rails g controller users index
# app/config/routes

Rails.application.routes.draw do
  root to: 'users#index'
  resources :users
end

Relembrando: resources em routes gera para gente as seguintes rotas:

Método Rota controller#action
GET /users users#index
GET /users/:id users#show
GET /users/new users#new
POST /users/:id users#update
DELETE /users/:id users#delete

Feito isso, agora iremos começar a configurar o template e a importar os arquivos html, css e js para dentro de nossa aplicação. Primeiro vamos baixar o template utilizando o link Sb Admin

De posse dos arquivos, iremos começar a inserir os stylesheets na nossa aplicação.

Relembrando: como falando no início do post, iremos salvar os arquivos do template nas pastas vendor/assets/stylesheets e vendor/assets/javascripts, pois são arquivos de terceiros. Estas pastas não existem e devem ser criadas por você.

Para começar, irei importar o arquivo startbootstrap-sb-admin-2-gh-pages/css/sb-admin-2.css, ele contém quase todo o estilo que precisamos. Para fazermos ele funcionar, precisamos de duas coisas

A partir de agora irei utilizar os conceitos explicados no início da publicação.

1. Adicionar o arquivo em vendor/assets/stylesheets/

2. Adicionar o import em app/assets/stylesheets/application.css

Feito isso, já podemos ver um grande avanço no estilo de nossa página. Para você ver que estamos avançando, faça o seguinte, pegue o arquivo index.html do SB Admin 2, copie o cole o conteúdo do body para dentro de app/views/users/index.html.erb (arquivo criado ai criar o controller de usuários) e então acesse a página localhost:3000, você deverá ver algo do tipo:

Pode perceber que alguns elementos estão faltando (calma, iremos corrigir isso já já) isso é devido a falta de algumas coisas bibliotecas javacritps necessárias para o funcionamento completo do template.

Analisando o template, percebemos que ele tem 3 grandes áreas, como vista na foto abaixo:

A área marcada de preta (sidebar) e a área marcada de vermelha (header ou topbar) costumam se repetir em todas as páginas do nosso dashboard e a parte que muda conforme navegamos é a área marcada de verde. Então iremos remover o código do sidebar e do header de nossas views e colocaremos no arquivo de layout, fazemos isso para reduzir a repetição de código e para tornar a aplicação mais fácil de manter, já que quando tivermos uma alteração em algum dessas duas partes (preta ou vermelha) do layout, basta alterar 1 arquivo.

A sidebar desse template começa assim:

<!-- Sidebar -->
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
  ...
</ul>

Após fazer alguns ajustes nosso arquivo users.html.erb ficará assim:

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'users', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'users', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <div id="wrapper">
      <!-- Sidebar -->
      <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
        ...
      </ul>
      <!-- End of Sidebar -->

      <%= yield %>
    </div>
  </body>
</html>

Podemos atualizar a página em localhost:3000 e ver que nada mudou, isso e ótimo, pois nossa sidebar já está instalada no layout e será copiada para qualquer view do controllerde usuários.

Agora precisamos fazer o mesmo para o header, precisamos encontrar o código html responsável por renderizar o header e jogá-lo dentro do layout de usuário

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'users', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'users', 'data-turbolinks-track': 'reload' %>

  </head>

  <body>
    <div id="wrapper">
      <!-- Sidebar -->
      <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
        ...
      </ul>
      <!-- End of Sidebar -->

      <!-- Content Wrapper -->
      <div id="content-wrapper" class="d-flex flex-column">
        <!-- Main Content -->
        <div id="content">
          <!-- Topbar -->
          <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
            ...
          </nav>
          <!-- End of Topbar -->

          <%= yield %>
        </div>
        <!-- End of Main Content -->
      </div>
      <!-- End of Content Wrapper -->
    </div>
  </body>
</html>

Após inserido o sidebar e o header, nosso arquivo de layout deve ficar como a foto acima. Lembrando que todo o conteúdo da view será adicionado pela a tag <%= yield %>.

Para finalizar, iremos importar todos os javascripts da página e o font-awesome (uma fonte de ícones maravilhosa, uso em quase todos meus projetos, você pode ver aqui: Font Awesome).

Começaremos importando o Font-Awesome via CDN, você pode encontrá-lo aqui

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" crossorigin="anonymous" />

Colocarei esse código no head de nosso template users.html.erb e isso será suficiente para fazer funcionar. Podemos atualizar a página e ver ícones em nossa página.

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'users', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'users', 'data-turbolinks-track': 'reload' %>
    
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" crossorigin="anonymous" />

  </head>

  <body>
    ...
  </body>
</html>

O procedimento para importar os javascripts é bem similar ao que fizemos para os stylesheets. Iremos começar criando um arquivo chamado users.js em app/assets/javascripts.

Salvaremos todos os arquivos JS que o index do SB Admin importa dentro de vendor/assets/javascripts. Você pode ver esses imports no final do arquivo:</p>

Assim, nossa pasta vendor, ficará assim:

E o arquivo users.js,ficará assim:

Para finalizar, precisamos fazer duas coisas:

  1. importar de nosso users.js no nosso layout (users.html.erb)
  2. Adicionar o arquivo users.js em assets.rb
# assets.rb

Rails.application.config.assets.precompile += %w( users.css  users.js )

Agora podemos reiniciar o server e atulalizar a página e veremos nossa página funcionando corretamente:

#Fim