Como usar o Cloud Storage com Ruby

Nesta parte do tutorial do Bookshelf, veja como o aplicativo de amostra armazena imagens no Cloud Storage.

Esta página faz parte de um tutorial com várias páginas. Para começar do início e ver as instruções de configuração, consulte Aplicativo Bookshelf em Ruby.

Como criar um intervalo do Cloud Storage

O Cloud Storage permite armazenar e exibir dados binários. Chamamos de intervalo um contêiner de alto nível para objetos binários.

Veja nas instruções a seguir como criar um intervalo do Cloud Storage. Intervalos são os contêineres básicos que armazenam dados no Cloud Storage.

  1. Em uma janela de terminal, digite o seguinte comando:

    gsutil mb gs://[YOUR-BUCKET-NAME]

    [YOUR-BUCKET-NAME] representa o nome do intervalo do Cloud Storage.

  2. Para visualizar imagens que foram carregadas no aplicativo Bookshelf, defina a lista de controle de acesso (ACL, na sigla em inglês) padrão do intervalo como public-read.

    gsutil defacl set public-read gs://[YOUR-BUCKET-NAME]

Como instalar dependências

Acesse o diretório getting-started-ruby/3-cloud-storage e insira o seguinte comando:

bundle install

Como definir as configurações

  1. Copie o arquivo de exemplo settings:

    cp config/settings.example.yml config/settings.yml
    
  2. Abra settings.yml para edição. Substitua os marcadores pelo seu projeto e pelos nomes do intervalo do Cloud Storage.

    Por exemplo, suponha que o nome do projeto seja my-project e o nome do intervalo seja my-bucket. Então, a seção default do seu arquivo settings.yml ficaria assim:

    default: &default
      project_id: my-project
      gcs_bucket: my-bucket
    
  3. Copie o arquivo de exemplo database:

    cp config/database.example.yml config/database.yml
    
  4. Configure o aplicativo de amostra para usar o mesmo banco de dados que você configurou durante a parte Como usar dados estruturados deste tutorial:

    Cloud SQL

    • Edite o arquivo database.yml. Remova os comentários das linhas da parte do Cloud SQL no arquivo.

       mysql_settings: &mysql_settings
         adapter: mysql2
         encoding: utf8
         pool: 5
         timeout: 5000
         username: [MYSQL_USER]
         password: [MYSQL_PASS]
         database: [MYSQL_DATABASE]
         socket: /cloudsql/[YOUR_INSTANCE_CONNECTION_NAME]
      
      • Substitua [MYSQL_USER] e [MYSQL_PASS] pelo nome de usuário e pela senha da instância do Cloud SQL criados anteriormente.

      • Substitua [MYSQL_DATABASE] pelo nome do banco de dados criado anteriormente.

      • Substitua [YOUR_INSTANCE_CONNECTION_NAME] pelo Instance Connection Name da instância do Cloud SQL.

    • Execute as migrações.

      bundle exec rake db:migrate
      

    PostgreSQL

    • Edite o arquivo database.yml. Remova os comentários das linhas da parte do PostgreSQL no arquivo. Substitua os marcadores your-postgresql-* pelos valores do banco de dados e da instância do PostgreSQL. Por exemplo, suponha que seu endereço IPv4 seja 173.194.230.44, seu nome de usuário seja postgres, sua senha seja pword123 e o nome do seu banco de dados seja bookshelf. Então, a parte do PostgreSQL do seu arquivo database.yml seria assim:

      # PostgreSQL Sample Database Configuration
      # ----------------------------------------
        adapter: postgresql
        encoding: unicode
        pool: 5
        username: postgres
        password: pword123
        host: 173.194.230.44
        database: bookshelf
      
    • Crie as tabelas e o banco de dados necessários.

      bundle exec rake db:create
      bundle exec rake db:migrate
      

    Cloud Datastore

    • Edite o arquivo database.yml. Remova os comentários da linha na parte do Cloud Datastore no arquivo. Substitua your-project-id pelo código do projeto do Google Cloud Platform. Por exemplo, se o código do projeto for my-project, a parte do Cloud Datastore do arquivo database.yml será assim:

      # Google Cloud Datastore Sample Database Configuration
      # ----------------------------------------------------
      dataset_id: my-project
      
    • Execute uma tarefa "rake" para copiar os arquivos do projeto de amostra para o Cloud Datastore.

      bundle exec rake backend:datastore
      

Como executar o app na máquina local

  1. Inicie um servidor da Web local.

    bundle exec rails server
    
  2. No navegador da Web, digite este endereço:

    http://localhost:3000

Agora, é possível procurar as páginas da Web do app e adicionar, editar e excluir livros.

Para sair do servidor da Web local, pressione Control+C.

Como implantar o app no ambiente flexível do App Engine

  1. Compile os recursos do JavaScript para produção.

    RAILS_ENV=production bundle exec rake assets:precompile
    
  2. Implante o app de amostra.

    gcloud app deploy
    
  3. No navegador da Web, digite o endereço a seguir.

    https://[YOUR_PROJECT_ID].appspot.com
    

Atualize o app e implante a versão atualizada com o mesmo comando usado para implantá-lo pela primeira vez. A implantação cria uma nova versão do app e a define como padrão. As versões mais antigas são mantidas, bem como as instâncias de VM associadas. Lembre-se de que essas versões do aplicativo e instâncias de VM são recursos passíveis de cobrança.

Reduza os custos excluindo as versões não padrão do app.

Para excluir uma versão do app:

  1. No Console do Cloud, acesse a página Versões do App Engine.

    Acessar a página "Versões"

  2. Marque a caixa de seleção da versão não padrão do app que você quer excluir.
  3. Clique em Excluir para remover a versão do app.

Para informações detalhadas sobre a remoção de recursos faturáveis, consulte a seção Como fazer a limpeza na etapa final deste tutorial.

Estrutura do app

O diagrama a seguir mostra os componentes do aplicativo e como eles se relacionam.

Exemplo de estrutura de dados binários

O aplicativo utiliza {storage_name_short}} para armazenar dados binários (imagens, nesse caso), mas continua usando um banco de dados estruturado para as informações do livro (Cloud Datastore, Cloud SQL ou PostgreSQL).

Noções básicas sobre o código

Esta seção analisa o código do aplicativo e explica como ele funciona.

Processar uploads de usuários

Para que os usuários possam fazer upload de imagens, o formulário de adição/edição foi modificado para permitir uploads de arquivos. Agora o formulário tem várias partes.

<%= form_for @book, :html => { :multipart => true } do |f| %>

Além disso, o formulário tem um campo novo para a imagem de capa de um livro.

<div class="form-group">
  <%= f.label :cover_image %>
  <%= f.file_field :cover_image %>
</div>

Conectar-se ao Cloud Storage

O aplicativo Bookshelf usa a gem google-cloud-storage para acessar os serviços do Google Cloud Platform (GCP). Ele cria uma conexão com o Cloud Storage por meio das credenciais locais que você adquiriu para sua estação de trabalho ou pelas credenciais ambientais durante a execução nas VMs do GCP.

O método de classe storage_bucket da classe Book retorna uma referência ao intervalo de armazenamento especificado pelas configurações no arquivo settings.yml. Esse intervalo é usado para armazenar imagens de capa.

require "google/cloud/storage"

class Book < ActiveRecord::Base

  def self.storage_bucket
    @storage_bucket ||= begin
      config = Rails.application.config.x.settings
      storage = Google::Cloud::Storage.new project_id: config["project_id"],
                                           credentials: config["keyfile"]
      storage.bucket config["gcs_bucket"]
    end
  end

Fazer upload para o Cloud Storage

A classe Book tem um método upload_image que é chamado sempre que um livro é criado. Em upload_image, com a chamada de image.save, um novo arquivo disponível para leitura pública é criado no intervalo do Google Cloud Storage usando o conteúdo de cover_image. Depois que a imagem é salva, o image_url do livro é atualizado para o URL público da imagem salva.

after_create :upload_image, if: :cover_image

def upload_image
  file = Book.storage_bucket.create_file \
    cover_image.tempfile,
    "cover_images/#{id}/#{cover_image.original_filename}",
    content_type: cover_image.content_type,
    acl: "public"

  update_columns image_url: file.public_url
end

Exibir imagens do Cloud Storage

Vale a pena fazer isso direto do Cloud Storage porque as solicitações aproveitam a infraestrutura de exibição global do Google e o aplicativo não precisa responder a solicitações de imagens, liberando ciclos de CPU para outras solicitações.

Para que uma imagem de capa fique visível no aplicativo, atualize a visualização do livro e do índice.

<div class="media">
  <div class="media-left">
    <img src="<%= @book.image_url %>">
  </div>
  <div class="media-body">
    <h4><%= @book.title %> | &nbsp; <small><%= @book.published_on %></small></h4>
    <h5>By <%= @book.author || "unknown" %></h5>
    <p><%= @book.description %></p>
  </div>
</div>

Excluir imagens

Quando você exclui um livro, a classe de modelo de livro verifica se ele tem uma imagem de capa salva no intervalo do Cloud Storage. Se houver uma imagem de capa, ela será excluída.

before_destroy :delete_image, if: :image_url

def delete_image
  image_uri = URI.parse image_url

  if image_uri.host == "#{Book.storage_bucket.name}.storage.googleapis.com"
    # Remove leading forward slash from image path
    # The result will be the image key, eg. "cover_images/:id/:filename"
    image_path = image_uri.path.sub("/", "")

    file = Book.storage_bucket.file image_path
    file.delete
  end
end

Atualizar imagens

Ao atualizar um livro, é possível trocar a imagem de capa. Se já houver uma imagem de capa, ela será excluída. A nova imagem será salva no intervalo do Google Cloud Storage.

before_update :update_image, if: :cover_image

def update_image
  delete_image if image_url?
  upload_image
end