# Setup de projeto Rails
###### tags: `tutorial` `rails` `docker`
---
[ToC]
## :hammer: Criando arquivos do Projeto
Para iniciar, temos que ter algumas coisas instaladas.
Inicialmente, vamos precisar do Node, yarn e alguns pacotes, como clientes de sql e de imagens. [Esse link](https://guides.rubyonrails.org/development_dependencies_install.html) contém as dependências.
Com todas as dependências necessárias prontas, está na hora de criamos o nosso projeto em Rails. No terminal, navegue até a pasta desejada e digite:
```sh
rails new pasta_do_projeto -d postgresql
```
Isso irá criar uma pasta chamada `pasta_do_projeto`, com todas os arquivos necessários. Com isso feito, abra a pasta com algum editor de texto que esteja familiarizado.
:::info
:bulb: **Dica:** Para mais informações do comando **`rails new`**, digite **`rails new --help`**.
:::
:::danger
:heavy_exclamation_mark: **Erros:** Alguns erros comuns nessa etapa estão normalmente relacionados a dependencia. Se o problema parece ser do JavaScript ou do Webpacker, certifique-se que o yarn e o node estejam instalados, e então execute:
```bash=1
yarn install
rails webpacker:install
rails webpacker:compile
```
:::
## :whale: Docker
:::info
:bulb: **Dica:** Se não quiser usar o Docker, pode instalar e executar as aplicações (postgres, redis e etc) na mão, só mudar o `.env` depois para os endereços locais.
:::
Antes de iniciarmos o nosso app, vamos configurar o [Docker](https://docs.docker.com/get-docker/).
Primeiro vamos criar três arquivos na pasta raiz do projeto:
- `Dockerfile`
- `docker-compose.yml`
- `.env`.
### Dockerfile
O arquivo `Dockerfile` serve para **definirmos as instruções** de como desejamos criar uma máquina no Docker. As instruções são uma mistura de comandos de terminal (tais como instalar pacotes) e configuração da imagem. Vamos começar colocando esse texto no `Dockerfile`:
```dockerfile=1
## 1. Configurações da Imagem
## 2. Instalação de pacotes
## 3. Preparando o Rails
## 4. Iniciando
```
Vamos **passo-a-passo**.
Como queremos fazer um projeto Rails, precisamos de um ambiente com Ruby. Felizmente, já temos algumas imagens que ja possuem um ambiente todo configurado com o Ruby. Uma lista completa pode ser vista [aqui](https://hub.docker.com/_/ruby).
Mas no momento, queremos o **Ruby 2.7.3**, então vamos trocar a linha `## 1. Configuração da Imagem` por isso:
```dockerfile=1
FROM ruby:2.7.3-alpine
```
:::info
:bulb: **Nota:** Esse sufixo `-alpine` significa uma versão mais enxuta do sistema.
:::
:::warning
:heavy_exclamation_mark: **Nota:** A versão **2.7.3** é a versão que usamos na Trusted. Mas lembre-se de que a versão que está no **`Dockerfile` deve** ser igual a que está no arquivo **`Gemfile`**, na linha que diz **`ruby '2.*.*'`**.
:::
O próximo passo é fazer a instalação de alguns pacotes que são necessários. Então substitua o segundo comentário por:
```dockerfile=3
RUN apk add --update --no-cache shared-mime-info tzdata yarn nodejs build-base postgresql-dev
RUN gem install bundler -v 2.2.14
```
A primeira linha instala algumas dependências na máquina, e a segunda instalar o `bundler`, que é uma ferramenta para instalar gemas (pacotes do Ruby).
Agora vamos configurar algumas dependências e arquivos necessários.
Troque o terceiro comentário por:
```dockerfile=7
RUN mkdir /app
WORKDIR /app
COPY Gemfile Gemfile.lock vendor ./
RUN bundle install
COPY . .
```
Esse é um pouco mais complexo, mas vamos lá. As 2 primeiras linhas servem para criar uma pasta chamada `app` e definir ela como raiz do projeto.
O primeiro comando `COPY` copia os arquivos `Gemfile` e `Gemfile.lock` e a pasta `vendor` para a pasta `app` que criamos antes.
Com esse arquivos copiados, podemos executar a atualização e instalação de pacotes, com o comando `RUN`.
O último `COPY` serve para copiar todos os arquivos de código para a pasta `app`.
Por fim, devemos definir como vamos iniciar o nosso servidor, ou mais conhecido como o *entry point* do serviço.
Para isso, troque o último comentário por:
```dockerfile=17
ENTRYPOINT ["./entrypoints/docker_entrypoint.sh"]
```
Essa linha indica que vamos utilizar o arquivo `entrypoint/docker_entrypoint.sh` como ponto de partida do serviço.
Como não temos esse arquivo ainda, vamos criá-lo. Crie a pasta `entrypoints` e então o arquivo `docker_entrypoint.sh` com o seguinte conteúdo:
```bash=1
#!/bin/sh
set -e
if [ -f tmp/pids/server.pid ]; then
rm tmp/pids/server.pid
fi
yarn install --check-files
bundle exec rails s -b 0.0.0.0
```
O código aqui é bem padrão, mas o que ele faz é remover o arquivo `server.pid` se existe (é um arquivo gerado pelo Rails) e então inicia o serviço com `rails s`.
:::info
:bulb: **Nota:** O **`bundle exec`** é para utilizar o contexto do *bundle*, e o **`-b 0.0.0.0`** é o IP que o serviço deve se atrelar (**`0.0.0.0`** é para que possa ser exportado para fora).
:::
O `yarn install` serve para atualizar as bibliotecas do JavaScript.
Nosso `Dockerfile` deve ficar parecido com esse:
```dockerfile=1
FROM ruby:2.7.3-alpine
RUN apk add --update --no-cache shared-mime-info tzdata yarn nodejs build-base postgresql-dev
RUN gem install bundler -v 2.2.14
RUN mkdir /app
WORKDIR /app
COPY Gemfile Gemfile.lock vendor ./
RUN bundle install
COPY . .
ENTRYPOINT ["./entrypoints/docker_entrypoint.sh"]
```
:::success
:heavy_check_mark: E com isso temos o nosso **`Dockerfile`** pronto, mas ainda precisamos do **`docker-compose.yml`** preparado para poder levantar o serviço.
:::
---
### docker-compose.yml
Esse arquivo serve para **definir o ambiente** que nosso `Dockerfile` deve executar. Se precisamos (e precisamos) de um banco de dados, podemos definir aqui. Se precisamos adicionar um *Load Balancer*, é aqui também. Para começar, precisamos de duas coisas, o nosso serviço e um banco de dados.
Por ser um `.yml`, ele segue uma [síntaxe específica](https://en.wikipedia.org/wiki/YAML).
Vamos começar com isso:
```yaml=1
version: "3"
services:
web:
database:
```
A primeira linha define que versão do Docker Compose queremos suportar.
A tag `services` define os serviços que queremos definir, e começamos com dois: o `web` e o `database`.
Para cada serviço que queremos, precisamos definir algumas coisas: como ele é executado, ambiente, volumes, dependências e portas de acesso:
- **Execução:** pode ser de duas formas: com `build` ou com `image`. `build` é usado quando queremos definir nosso próprio `Dockerfile`, ou seja, nós mesmos vamos construir nossa imagem. `image` é quando queremos usar uma imagem padrão já existente.
- **Ambiente:** é a definição de variáveis de ambiente, como senhas, hosts, usuários entre outros.
- **Volumes:** são usados para guardar os dados gerados (se não definirmos um volume para o nosso banco de dados, sempre que reiniciamos ele perdemos tudo).
- **Portas de acesso:** servem para quando queremos acessar os serviços de fora.
- **Dependências:** são auto explicatórias, nos só podemos executar alguns serviços se outros já estiverem levantados
Vamos começar com o `web` que é o nosso Rails.
Como queremos criar a nossa própria imagem, vamos utilizar o `build`, depois do `web:`:
```yaml=5
build: .
```
Essa é a configuração padrão do `build`, e ele assume o dockerfile chamado `Dockerfile`. Se quisermos especificar outro, podemos fazer desse jeito:
```yaml=5
build:
context: .
dockerfile: OutroDockerfile
```
Agora vamos configurar o volume que vamos usar. Neste caso, o volume serve para termos acesso ao cófigo fonte e poder executar o app.
No nosso `Dockerfile`, nós definimos que a pasta se chama `app` na linha `WORKDIR /app`. Então é essa que vamos usar. Também iremos criar o volume `node_modules`, que são onde ficam as bibliotecas Javascript. Criamos isso para facilitar o desenvolvimento futuro.
Para configurar o volume, vamos adiconar isso:
```yaml=8
volumes:
- .:/app
- /app/node_modules/
```
:::info
:pushpin: Para entender melhor a definição de volumes no Docker e Docker Compose, sugiro **[esse link](https://docs.docker.com/storage/bind-mounts/)**.
:::
Não podemos nos esquecer de que como o Rails é um serviço web, precisamos acessar ele.
Podemos fazer isso configurando as portas, assim:
```yaml=10
ports:
- '3000:3000'
```
Essa linha mapeia a porta 3000 do hospedeiro (nosso computador) para a porta 3000 do container, assim temos acesso ao servidor do Rails.
Agora iremos definir alguns valores de ambiente, e esse é bem simples:
```yaml=12
env_file: .env
```
O `env_file` define o arquivo de ambiente, normalmente é o `.env`. Mais tarde vamos passar por ele.
Por fim, vamos vamos definir as dependências do nosso Rails.
Atualmente temos apenas o PG (que não configuramos, mas já está definido como `database`). Então acrescentamos:
```yaml=13
depends_on:
- database
```
No final, nosso serviço `web` deve ficar mais ou menos assim:
```yaml=4
web:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/app
- /app/node_modules/
ports:
- '3000:3000'
env_file: .env
depends_on:
- database
```
Agora vamos configurar o Postgres!
Como ele é um banco de dados comum e bem usado, já existem imagens prontas para ele, basta usar. Para isso, vamos adicionar logo após o `database:`:
```yaml=17
image: 'postgres:12.1-alpine'
```
E como queremos que o nosso PG salve os dados, temos que criar um volume para ele:
```yaml=18
volumes:
- postgres:/var/lib/postgresql/data
```
E assim como no `web`, queremos definir algumas varáveis de ambiente.
Então vamos definir o `env_file`:
```yaml=20
env_file: .env
```
Como usamos um volume chamado `postgres`, devemos definir ele.
Para isso, no final do arquivo basta adicionar:
```yaml=22
volumes:
postgres:
```
Com isso, o `docker-compose.yml` deve ficar parecido com esse:
```yaml=
version: "3"
services:
web:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/app
- /app/node_modules/
ports:
- '3000:3000'
env_file: .env
depends_on:
- database
database:
image: 'postgres:12.1-alpine'
volumes:
- postgres:/var/lib/postgresql/data
env_file: .env
volumes:
postgres:
```
### Configuração de Ambiente
O `.env` é um arquivo bem comum para **definição de variáveis de ambiente**.
Sua síntaxe é bem simples: `NOME=valor`
No nosso ambiente, queremos configurar o modo de execução do Rails e login do PG, assim:
```env=1
RAILS_ENV=development
POSTGRES_PASSWORD=dogo_dogo_hotdogo
POSTGRES_USER=kitana
DATABASE_HOST=database
DATABASE_PORT=5432
DATABASE_NAME=my_namespace
```
A primeira linha define que o Rails deve ser executado em modo de desenvolvimento.
O segundo bloco de linhas são variáveis do próprio Postgres, onde definimos nosso usuário e senha.
O terceiro bloco são variáveis que iremos utilizar no Rails para pode nos conectar no PG.
`DATABASE_HOST` é o endereço do banco de dados. Como estamos num contexto Docker, podemos utilizar o próprio nome do serviço como endereço, em vez de tentar adivinhar o IP dele.
`DATABASE_PORT` é a porta do Postgres. 5432 é a padrão.
`DATABASE_NAME` é o nome do *namespace* do nosso banco de dados. O ideal é ter um *namespace* por projeto para deixar tudo bem separado.
Agora temos que fazer o Rails utilizar essas configurações.
Abra o arquivo `config/database.yml` e substitua todo o conteúdo por isso:
```yaml=1
default: &default
adapter: postgresql
encoding: unicode
database: <%= ENV['DATABASE_NAME'] %>
port: <%= ENV['DATABASE_PORT'] %>
host: <%= ENV['DATABASE_HOST'] %>
username: <%= ENV['POSTGRES_USER'] %>
password: <%= ENV['POSTGRES_PASSWORD'] %>
pool: <%= ENV['RAILS_MAX_THREADS'] || 5 %>
timeout: 5000
development:
<<: *default
test:
<<: *default
production:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
```
## :rocket: Iniciando o Rails via Docker
Com tudo configurado, só falta levantar os nossos serviços!
Primeiro de tudo, como nós não vamos usar imagens prontas para o `web`, temos que construir ela. Para isso, usamos o comando `docker-compose build`.
Assim que for construido, irá aparecer algo como
```shell
Successfully tagged pasta_do_projeto_web:latest
```
Para levantar os serviços, basta digitar `docker-compose up`. O `up` também suporta o parâmetro `--build` que força reconstruir a imagem.
Assim que executar o `up`, um erro irá aparecer, algo assim:
```shell
ERROR: for web Cannot start service web: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"./entrypoints/docker_entrypoint.sh\": permission denied": unknown
ERROR: Encountered errors while bringing up the project.
```
O que aconteceu foi que não temos permissão de execução no arquivo `entrypoints/docker_entrypoint.sh`. Para corrigir isso, basta executar `chmod +x entrypoints/docker_entrypoint.sh` e então `docker-compose up` de novo, e agora podemos entrar no nosso serviço em [localhost:3000/](http://localhost:3000), mas provavelmente você vai ver algo como isso: ![](https://i.imgur.com/7rqBRhT.png)
Essa é a página de erro do Rails, ela aparece apenas para quando estamos no modo de desenvolvimento. Vamos entender o que ela diz.
`ActiveRecord::NoDatabaseError` é a classe do erro que foi levantado. Neste caso, é bem sucinto: não temos um banco de dados.
`FATAL: database "my_namespace" does not exist` é uma descrição melhor do erro. Aqui também é bem explicito: o Rails espera que o nosso PG tenha um banco de dados chamado "my_namespace".
Para corrigir isso, basta executar o comando de preparação do Banco de Dados do Rails. Para isso, entre em um outro terminal (o `docker-compose up` deve estar executando!) e digite:
```shell
docker-compose exec web rails db:setup
```
## :heavy_check_mark: Sucesso
Com o servidor Rails levantado, se acessar o [http://localhost:3000](http://localhost:3000/) você deve ver isso: ![](https://i.imgur.com/wIBvBoL.png)
:::success
:rocket: **Próximo passo:** Siga o link ➜ [Criando a Home com Bootstrap](https://hackmd.io/UWCBZXZ-QvSW_zS_5_Bfog)
:::