owned this note
owned this note
Published
Linked with GitHub
# Processamento em plano de fundo com Sidekiq
###### tags: `tutorial` `rails` `docker` `sidekiq`
---
[ToC]
## Introdução
O [Sidekiq](https://github.com/mperham/sidekiq) é uma gema bem conhecida no Rails para processamento em segundo plano. Ele usa um banco de dados chamado REDIS para armazenar os dados dos `workers`. O Sidekiq também tem as versões Pro e Enterprise, que são pagas. Mas para o que queremos podemos usar a versão gratuita sem problemas.
Para usar o Sidekiq, vamos:
1. Adicionar o REDIS e uma máquina do Sidekiq no ambiente Docker
2. Criar nosso `worker`
## Adicionando o REDIS e Sidekiq no Docker
Vamos adicionar essas duas máquinas no nosso `docker-compose.yml`. Felizmente, o REDIS já possui uma imagem pronta, é só usar. O Sidekiq precisa de um pouco mais de configuração. Então, antes do último `volumes:`, vamos adicionar ambas as máquinas:
```yaml=22
redis:
image: 'redis:6-alpine'
sidekiq:
build:
context: .
dockerfile: Dockerfile
depends_on:
- web
- database
- redis
volumes:
- .:/app
env_file: .env
entrypoint: ./entrypoints/sidekiq_entrypoint.sh
```
Nada além do que ja vimos no primeiro tutorial. Agora temos que criar o arquivo `entrypoints/sidekiq_entrypoint.sh` e adicionar esse código:
```shell=
#!/bin/sh
set -e
if [ -f tmp/pids/server.pid ]; then
rm tmp/pids/server.pid
fi
bundle exec sidekiq
```
Também nada muito diferente, exceto que executamos `sidekiq` em vez de `rails s`.
Agora precisamos instalar a gema do Sidekiq. Então no nosso `Gemfile` vamos adicionar:
```ruby=26
gem 'sidekiq'
gem 'redis'
```
E por fim, vamos atualizar nosso arquivo de variáveis de ambiente, o `.env` com essas duas linhas no final:
```shell=
REDIS_HOST=redis
REDIS_URL=redis://redis:6379/1
```
## Criando o Worker
*Workers* são as classes que executam código em segundo plano. Eles seguem um padrão e devemos respeitá-lo. Primeiro que ele deve incluir o módulo `Sidekiq::Worker`, segundo que ele deve definir um método chamado `perform` e seus argumentos só podem ser de tipos básicos (String, Hash, JSON, Número, etc) e nada de classes mais avançadas, por exemplo o nosso `Image`.
O *worker* que vamos fazer vai realizar um processamento na nossa imagem para extrair as cores dominantes dela.
O código de extrair as cores é de uma gema, então vamos adicioná-la ao nosso `Gemfile`:
```ruby=28
gem 'colorama'
```
Para instalar a gema, precisamos do `imagemagick` no sistema. Se tiver ela instalada, só executar `bundle install`. Para quem tem instalado via Docker, vamos precisar:
```bash=
docker-compose build
docker-compose up
```
E agora vamos criar nosso worker! Crie a pasta `app/workers` e crie o arquivo `app/workers/image_process_worker.rb` e coloque isso:
```ruby=
class ImageProcessWorker
include Sidekiq::Worker
def perform(image_id)
end
end
```
:::info
:bulb: **Nota:** Também existe o padrão de chamar os workers de jobs, e no nosso caso poderiamos ter criado o **`app/jobs/image_process_job.rb`**. Ambos estão certos, mas o importante é definir um padrão.
:::
Esse worker não faz muita coisa ainda, mas antes dele fazer, precisamos de um lugar para salvar os dados que extraimos.
Então vamos gerar uma **migração** para alterar a tabela `Image` com alguns campos a mais para poder salvá-los. No terminal, rode:
```shell=
rails g migration AddColorsToImage detail_color:string primary_color:string secondary_color:string background_color:string
```
Se estiver apenas com o Docker com o `ImageMagick`, precisamos executar essa migração via Docker:
```bash=
docker-compose up
docker-compose exec web rails g migration AddColorsToImage detail_color:string primary_color:string secondary_color:string background_color:string
```
Já podemos levantar nosso App e rodar a migração com `docker-compose up` e `docker-compose exec web rails db:migrate`.
Então agora podemos atualizar nosso worker para fazer algo mais útil do que nada. No método `perform` nós precisamos fazer algumas coisas. A primeira é buscar os dados da imagem, processar a imagem e então salvar os dados.
Primeiro adicionamos
```ruby=5
image = Image.find(image_id)
```
para buscar a imagem, então
```ruby=6
colors = Colorama.extract_from_file(ActiveStorage::Blob.service.path_for(image.file.key))
```
para fazer o processamento em si. O `ActiveStorage::Blob.service.path_for` busca o arquivo para carregar a imagem. E por fim
```ruby=8
image.update(
primary_color: "##{colors[:primary].hex}",
secondary_color: "##{colors[:secondary].hex}",
detail_color: "##{colors[:detail].hex}",
background_color: "##{colors[:background].hex}"
)
```
para atualizar a imagem com as cores.
## Enfileirando o *Worker*
Para que as nossas imagens passem por esse fluxo, precisamos enfileirar elas quando são criadas. Para isso, no nosso `ImageController` vamos adicionar esse códgio no nosso método `create`, logo depois de `if @image.save`:
```ruby=
ImageProcessWorker.perform_async(@image.id)
```
O mesmo código vai para o método `update`, logo depois de `if @image.update(image_params)`.
:::info
:memo: **Lembrete:** **`create`** e **`update`** são chamados quando a imagem é criada ou atualizada, respectivamente.
:::
:::success
:rocket: **Sucesso:** A partir daqui já temos um fluxo completo do Sidekiq! Porém ainda precisamos de mais coisas para deixar o processo amigável (por exemplo, não podemos ver as cores geradas). O resto do tutorial é para isso.
:::
## Mostrando o resultado
Vamos colocar as cores buscadas quando o usuário visualiza a imagem em detalhes, então vamos abrir o arquivo `app/views/images/show.html.erb` e trocar o código por isso:
```htmlembedded=
<div class="card mx-auto" style="width: 50%; min-width: 200px; margin-top: 1em; background-color: <%= @image.background_color %>">
<%= image_tag @image.file, class: 'card-img-top' %>
<div class="card-body">
<h2 class="card-title" style="color: <%= @image.detail_color %>"><%= @image.title %>!</h5>
<p class="card-text" style="color: <%= @image.primary_color %>">Primary color</p>
<p class="card-text" style="color: <%= @image.secondary_color %>">Secondary color</p>
<div class="btn-group d-flex">
<%= link_to 'Editar', edit_image_path(@image), class: 'btn btn-warning' %>
<%= link_to 'Voltar', images_path, class: 'btn btn-secondary' %>
</div>
</div>
</div>
```
E assim, quando a imagem termina de processar, recarregamos a página e temos:
![](https://i.imgur.com/Zbol0DH.png)
:::success
:rocket: **Próximo passo:** Siga o link ➜ [Atualizando a página automaticamente com ActionCable](https://hackmd.io/4hUGe1TmRVShHhUfu9amAw)
:::