# 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) :::