ActiveStorage: Usando na prática a nova ferramenta de upload do Rails


O que vamos aprender?

o ActiveStorage é um novo recurso que virá na versão 5.2 do Rails para facilitar o upload de arquivos, e nós vamos aprender como utiliza-lo na prática (criando um pequeno projeto).

 

  • Como fazer upload de arquivos com o ActiveStorage
  • Como deixar nossos uploads assíncronos (com indicador de progresso)
  • Como enviar nossos arquivos direto para o S3 da Aws
  • Como gerar variações das nossas imagens

 

Bora?

 

Ferramentas

  • Ruby 2.4.2
  • Rails 5.2.0.beta2
  • ActiveStorage (parte do Rails 5.2)
  • ImageMagick (software para modificar imagens)

Resumo do app

Neste artigo vamos criar um pequeno App onde os usuários poderão compartilhar arquivos entre si de maneira organizada através do upload de arquivos para repositórios (buckets).

Para fazer isso vamos criar as seguintes features:

  • Criação de um bucket
  • Remoção de um bucket
  • Upload de novos arquivos para um bucket
  • Remoção de um arquivo
  • Download de um arquivos

 

Projeto desenvolvido no Github: https://github.com/OneBitCodeBlog/awesome_bucket

 

Então, vamos começar a codar! \o/


Mãos a obra

 

Preparação

Primeiro, vamos criar nosso app e destacar algumas coisas a respeito do ActiveStorage

1. Para instalar Rails 5.2.0.beta2, vamos executar no terminal

2. Criaremos um novo app rails

3. Logo na criação do app, assim que o bundle é concluído, aparece uma mensagem de criação de um migration para o Storage, como na linha 07 abaixo:

4. Agora vamos entrar no diretório do app

5. Para saciar a curiosidade, vamos abrir o arquivo de migration to ActiveStorage, ele contém (db/migrate/<datetime>_create_active_storage_tables.active_storage.rb):

Olhe o que o AS faz: Cria duas tabelas, uma para armazenar as informações do arquivo (nome, tipo de conteúdo etc) e outro para armazenar as informações a respeito do model que contém os arquivos.

Eu sei que estas “informações a respeito do model que contém arquivos” ficou um pouco confuso, mas  vamos entender melhor daqui a pouco com código =)

6. Além destas tabelas o active storage vem com uma arquivo de configuração em config/storage.yml:

7. E com esta linha configurada em development.rb (config/environments/development.rb)

O arquivo storage.yml é usado para informar ao ActiveStorage onde ele vai armazenar o seu arquivo. Inicialmente ele vem configurado localmente para a pasta storage na raiz do seu projeto e na pasta tmp/storage para testes.

O conteúdo do arquivo development.rb foi exibido para vermos que, por padrão, ele seleciona o storage de nome local para utilizar no upload. Podemos colocar esta mesma linha em test.rb e production.rb, conforme o que quisermos configurar em cada ambiente.

Há algumas linha comentadas no storage.yml neste snippet. Exibí estas linhas porque o ActiveStorage já vem preparado para upload em serviços cloud Amazon S3, Azure e Google Cloud Services. Mas, vamos começar utilizando o armazenamento local.

Agora vamos à estrutura do app.

Criando a estrutura do app

1. Primeiro, vamos criar um scaffold para o Bucket

2. Rodar os migrations

3. No model Bucket, adicionaremos a seguinte linha (app/models/bucket.rb):

4. E colocaremos o rota raiz para o index do BucketsController (config/routes.rb):

Nesta parte simplesmente foi criado um scaffold para o nosso bucket, executado as migrations e, por fim adicionamos o has_many_attached, que é o que vai dizer que o nosso model Bucket possuirá anexos e terá o nome de :files. Por último, colocamos a nossa rota principal como sendo a action :index do BucketsController criado com o scaffold.

Era nesse ponto que eu queria chegar para explicar melhor como ActiveStorage está trabalhando com o nosso model.

 

O que ocorreu no model Bucket?

Bem, quando colocamos em nosso model o has_many_attached :files, um atributo chamado :files é criado e recebe um objeto do tipo ActiveStorage::Attached. Este objeto juntamente com os models ActiveStorage::Blob, ActiveStorage::Attachment e outros é que o vai nos prover uma imensa gama de funcionalidades e facilidades para usar o AS.

No lugar de has_many_attached, também poderíamos ter usado o has_one_attached :file, para que o model Bucket tenha apenas um anexo. Ou seja, no lugar de termos uma lista de anexos, haveria apenas um.

 

Criando um formulário de Upload

Com o scaffold, as ações de CRUD do Bucket foram criadas sem problemas.

Agora vamos criar um controller que será responsável por recepcionar os uploads e associá-los ao Bucket. Então, vamos aos códigos 🙂

 

1. O primeiro passo  um controller com a action new que será utilizada para renderizar o formulário de upload

2. Vamos ajustar a nossa rota para o controller FileUploads (config/routes.rb):

Primeiro vamos remover o registro criado pelo generator que é este conteúdo:

Depois colocar o :file_uploads como rota aninhada (nested route) das rotas do Bucket, ficando desta forma:

Deixado o nosso arquivo de rotas desta forma:

Perceba que no resources do :file_uploads, foram colocadas três actions: :new, :create e :destroy. A :create será responsável por recepcionar os arquivos que forem enviados pelo formulário renderizado na action :new. E a :destroy, para remover um arquivo.

3. Como temos uma rota aninhada, vamos precisar carregar também o Bucket na action :new.

Em FileUploadsController, a action :new ficará da seguinte forma (app/controllers/file_uploads_controller.rb):

4. Por último, criaremos um formulário para a action :new (app/views/file_uploads/new.html.erb):

Perceba que no formulário há apenas o campo de upload e um botão para submit. Neste campo upload, estamos utilizando um helper file_field do Rails para renderizar um campo de anexo. Precisamos também da URL, já que não é um rota padrão do model Bucket, e setar local: true para que o formulário seja enviado de forma síncrona. Lembrando que não é obrigatório que o formulário seja síncrono, colocamos esta configuração apenas para acompanharmos o submit do formulário sem a necessidade de utilizar outros recursos.

Com este passo feito, agora podemos criar o código para recepcionar os arquivos enviados pela action :new.

 

Recepcionando os arquivos

Para recepcionar os arquivos, vamos criar uma action :create com os passos para receber arquivos no controller FileUploads, alterar a view :show do Bucket para listar os arquivos adicionados e adicionar um link para enviar mais arquivos

1. Criaremos a action :create no FileUploadsController com o seguinte conteúdo (app/controllers/file_uploads_controller.rb)

Olha como o ActiveStorage já adiantou nosso trabalho: ao  usar o has_many_attached :files, o atributo :files, agora sendo acompanhado pelo ActiveStorage, tornou disponível, dentre outros, o método :attach. Para chamar este método, podemos passar diretamente o parâmetro que foi recebido do formulário, sem a necessidade de nenhuma alteração.

2. Vamos deixar a view :show do Bucket da seguinte forma (app/views/buckets/show.html.erb):

Nesta view, adicionamos alguns itens a mais: um botão que vai nos direcionar para a página onde fazemos um upload e uma lista de arquivos que aquele Bucket já tem. Perceba que dentro do link_to utilizamos mais duas coisas: file.filename, para acessar o nome do arquivo e um método url_for que recebe como parâmetro um registro do :files do model Bucket e já retorna uma url para o arquivo.

 

Removendo um arquivo enviado

O AS também traz um meio de removermos facilmente um arquivo que foi anexado atrávés do método :purge que é chamado também no objeto de anexo, no nosso caso com o nome :files.

Para podemos remover o arquivo, faremos o passo-a-passo abaixo

1. Apenas relembrando, já temos uma rota para o :destroy do FileUploadsController (config/routes.rb):

2. Agora vamos criar a action :destroy no nosso controller de FileUpload (app/controllers/file_uploads_controller.rb):

3. Por último, vamos adicionar um “X” na frente do nosso arquivo para que possamos clicar e remover. Para isso, a view :show do Bucket ficará da seguinte forma (app/views/buckets/show.html.erb):

E está pronta a exclusão. Agora toda vez que houver um arquivo e clicarmos no “X”, aparecerá uma mensagem de confirmação e ele será excluído.

Ah, uma curiosidade. O AS também vem com uma função chamada :purge_later que podemos usar igual o :purge, mas em vez de excluir instantaneamente, ela vai iniciar um Job do Rails que vai fazer o trabalho. É uma funcionalidade muito interessante para excluir arquivos grandes ou até mesmo de algum serviço na nuvem.

*Para saber mais sobre Jobs: Dominando o uso de Jobs no Rails


Bem, galera, está completo o básico nosso app \o/, agora bora dar um start no server com rails s e testar =D

 

Trabalhando com Imagens no AS

Uma das facilidades que o ActiveStorage nos oferece vem de uma ferramenta chamada ImageMagick. Com ela é possível fazer transformações de um arquivo de imagem como por exemplo redimensionar seu tamanho.

Como ele nos oferece isso? É bem simples, existe a gem MiniMagick que se conecta ao ImageMagick instalado no sistema para alterar as imagens. Para integrar isso com o ActiveStorage nós utilizamos um recurso chamado variants.

Primeiro, vamos instalar a gem Mini Magick. Adicione no seu Gemfile:

*Lembrando que você precisa ter o imageMagick instalado

Agora vamos instalar

Vamos usar como exemplo o código que adicionamos na view :show do Bucket (app/views/buckets/show.html.erb):

Se quiséssemos usar os variants, poderíamos fazer este código da seguinte forma:

O que este file.variant faz, na verdade, é pegar a imagem original e redimensionar (:resize) para o tamanho que eu especifiquei. Dentro do variant há diversas outras opções a serem utilizadas.

Mas, cuidado!! Se vc tentar utilizar variant num arquivo que não seja do tipo imagem, um erro vai ser gerado no seu app.

E para não ficarmos limitados a usar apenas imagens, por causa do variant, voltaremos view :show como estava antes, sem o variant:

 

O Direct Upload

Imagine que incrível seria podermos fazer o upload de um arquivo de forma assíncrona pro nosso servidor e melhor, podermos acompanhar o progresso de upload.

Bem, agora podemos!

O AS veio com uma funcionalidade incrível chamada Direct Upload. Quando utilizamos esta funcionalidade, ao darmos um submit no formulário, todos os arquivos serão enviados e salvos no servidor de forma assíncrona e só depois que o upload for concluído que o formulário será, de fato, enviado.

E fazer isso é muito, muuito fácil,
Então vamos pra melhor parte: codar!

Vamos seguir um passo a passo para configurar o Direct Upload para usarmos localmente:

1. Primeiro, vamos instalar a gem jquery-rails para facilitar o bind de eventos. Para isso, adicionaremos a gem jquery-rails ao seu Gemfile

2. Importaremos o jquery no application.js, que ficará da seguinte forma (app/assets/javascripts/application.js):

3. Depois, abriremos a view de upload e substituiremos o código pelo que está abaixo (app/views/file_uploads/new.html.erb):

Nesta view de upload, foi adicionado um h4 que será utilizado para mostrar o progresso de upload (sim, podemos fazer isso com o direct upload :D) e, além disso, adicionamos um direct_upload: true no input file.

Esta view, da forma como está, já está com o Direct Upload habilitado e funcionando. Ela já está enviado o arquivo antes de submeter o formulário como um todo (você pode checar nos logs do server). Mas como queremos trazer mais conteúdo, seguiremos adiante 🙂

4. Adicionaremos seguinte script ao application.js (app/assets/javascripts/application.js):

Ao colocamos direct_upload: true num campo de anexo de arquivos (input file), o javascript do Active Storage se encarrega de colocar vários eventos no formulário e no campo de anexo de arquivo que sao invocados no momento do submit.

Um deles é o direct-uploads:start que está no próprio formulário, que é chamado logo quando clicamos no botão para dar o submit no formulário. Utilizamos para poder exibir o campo h4 onde mostraremos o progresso do upload.

O outro evento que estamos utilizamos é o direct-upload:progress que está no próprio campo de anexo e é chamado conforme o arquivo vai sendo enviado para o servidor. A cada progresso que o arquivo faz, este evento é chamado e a porcentagem do progresso vem dentro de event.detail.progress, cujo valor colocamos direto no span do h4.

É necessário utilizar o event.detail.progress porque o progresso é enviado dentro dos detalhes do próprio evento.

 

P.S.: nas documentações do AS, tem uma lista de todos os eventos usado para o Direct Upload. E todos utilizam este mesmo formato de resultado como detalhes do event.

 

E a cereja do Bolo: Upload DIRETO para a Nuvem

Para fazer um upload direto pra nuvem, sem que o arquivo passe pelo nosso servidor antes, é bem simples.

Primeiro, é necessário habilitar o direct upload no campo de anexo. E nós já fizemos isso no passo anterior. Agora, apenas o que precisaremos fazer é configurar o nosso storage para fazer um upload para algum serviço de cloud.

Nesta demonstração, vamos utilizar um exemplo de upload para o Amazon S3.

1. Primeiro, será necessário que você crie uma conta na Amazon.

2. Depois, você terá que ir até o serviço Amazon S3 e criar um bucket com o nome que desejar. No nosso caso, criamos o bucket com nome onebitcode em umas das regiões.

3. É necessário que você mude as configurações de CORS na aws para permitir o upload vindo da sua URL.

4. Depois adicionaremos a gem da AWS para o S3 no Gemfile:

5. Instalaremos a gem rodando:

6. As informações necessárias para upload no AmazonS3 são: Access Key, Secret Key, region e nome do bucket. Vamos adicionar estas informações no storage.yml da seguinte forma (config/storage.yml):

Neste arquivo, criamos um storage com nome amazon, que utilizará o serviço S3 (nome padrão do AS), com as informações secret_key_id, secret_access_key e region (adicionar as suas) para fazer uploads no bucket onebitcode.

7. Nosso último passo, informaremos ao Rails que queremos utilizar este storage fazendo a seguinte alteração nas configurações de desenvolvimento (config/environments/development.rb):

Substituindo a seguinte linha

Por esta:

Nesta configuração em development.rb, estamos dizendo para o Rails utilizar no ambiente de development o storage com nome amazon. Mas o interessante seria aplicar esta alteração somente em produção (production.rb) para que não seja cobrado a utilização ao fazer uploads apenas para fins de teste.

8. Reiniciar o seu servidor e testar o/

 

Conclusão

O ActiveStorage veio como uma solução incrível para que possamos finalmente relaxar um pouco quando formos tratar de upload de arquivos (Ufa!). Creio que a ferramenta por ser nova, ainda tenha um caminho pela frente, mas com estes recursos que acabamos de ver e que já estão no forno pronto pra sair na próxima versão do Rails já podemos ver que é uma solução que veio pra ficar. (o alívio é grande).

E não esqueçam de ler o README do ActiveStorage e montar soluções incríveis com soluções locais ou em nuvem =D

Tem também o repositório do app que criamos no post:

https://github.com/OneBitCodeBlog/awesome_bucket

 

Obs: Galera, o has_many_attached e o has_one_attached criam uma associação entre o model do nosso app e os models do AS. Então, se excluirmos um Bucket agora, além dele, todos os registros dos arquivos e os próprios arquivos físicos serão excluídos. Resumindo, ele funciona como um depedent: :destroy das associações do Rails. E isso vale tanto para o storage local quanto para o storage na nuvem. Bem legal né?

 

Pessoal tá rolando a Semana Super Full Stack que é um mini curso do OneBitCode onde você vai aprender a criar do zero uma rede social usando Ruby On Rails, bora participar?

 

É isso aí, galera! Qualquer dúvida, estamos a disposição para ajudar! : )

Nos vemos no próximo conteúdo!

Janeiro 16, 2018

10 responses on "ActiveStorage: Usando na prática a nova ferramenta de upload do Rails"

  1. Leo, sugiro corrigir a informação com a instrução para baixar o Rails na versão beta2:

    gem install rails -v ‘5.2.0.beta2’

    No seu tá faltando o zero.

  2. Parabéns Moreto, ótimo post.
    Abs

  3. Muito bom o post!

  4. Tem um erro, aonde tu edita o show.html.erb.
    Se colocar como tu mandou “bucket_file_upload_path” da erro de rotas.
    O correto é “bucket_file_uploads_path”. uploads no plural.

  5. Na parte do Direct Upload, tem um erro no codigo JS. Esta assim:
    $(“h4.progress span.progress_count”).html(event.detail.progress);
    O que gera um erro de sintaxe ao executar. Tirando, funciona dai:
    $(“h4.progress span.progress_count”).html(event.detail.progress);

    O problema é esse perdido ali no meio. Fácil de resolver, mas quem não entende se perde facil.
    E isso acontece nas duas linhas que tem o h4.progress.

  6. No meu ultimo comentario, pelo visto, pegou a tag do problema pra formatar o texto em HTML.
    Eu nao consigo editar o comentario, então se puderem arrumar, é a tag de bold .

Deixe uma resposta

Feito com s2 por OneBitCode
%d blogueiros gostam disto: