Ruby on Rails Fullstack

O maravilhoso mundo de
ActiveRecord

O problema


# inicialização PITA do BD
DB = SQLite3::Database.new("db/database.sqlite3")

# Inserção de um objeto (em Ruby) no BD
cart = Cart.new(owner: 'Jon Snow')
DB.execute("INSERT INTO 'cart' (owner) VALUES ('#{cart.owner}')")

# Recuperação de um objeto (no BD) para ruby
# 1. pegue as tasks do BD
rows = DB.execute('SELECT * FROM tasks')

# 2. para cada linha recuperada, crie um novo Task. Guarde todos os
# tasks numa array
tasks = rows.map do |row|
  # 'done' será 0 ou 1 (no BD). Para transformá-lo em boolean...
  row['done'] = !row['done'].zero?
  Task.new(row)
end

A solução


# BD já configurado (sem ter que inicializar)

# ========== Inserção de um objeto (em Ruby) no BD ===========
cart = Cart.new(owner: 'Jon Snow')
cart.save # => salva no BD e fim

# ======== Recuperação de objetos (no BD) para ruby ==========
# pega todas as linhas do BD, transforma tudo em Task e guarda
# em uma Array
tasks = Task.all 


Just like that

First things firts

Precisamos configurar umas coisas antes

Configurando o projeto

Para nos ajudar, precisamos do ActiveRecord


$ gem install activerecord

Vamos acelerar o processo

Clonem o repositório abaixo. Algumas configurações já estão prontas (não é preciso se preocupar com as mesmas)

https://github.com/RafaelAlonso/active-record-101

Pontos importantes

  • Rake -T

    • Mostra todos os comandos que o rake pode fazer (com uma pequena desc do que fazem)

  • Gemfile e Gemfile.lock

    • Todas as gems que o projeto irá utilizar. As mesmas são 'instaladas' localmente ao rodar '$ bundle install' no terminal

Pontos importantes

  • config/application.rb

    • nosso app.rb!

  • config/database.yml

    • YAML é similar ao JSON, exceto pela sintaxe. database.yml guarda as informações sobre o Banco de Dados que iremos utilizar (nesse caso, o tipo do Banco [Sqlite3] e o arquivo do mesmo)

Pontos importantes

  • db/migrate/

    • É onde colocaremos as modificações feitas na estrutura do nosso BD (novas tabelas ou colunas, alteração de nomes/tipos, remoção, etc.)

  • db/seed.rb

    • Código para a população inicial do BD

 

Veremos os dois melhor daqui a pouco!

Estamos prontos para começar

Configurando o BD

1º: criar o BD

rake tem uma task para isso!


$ rake db:create # cria o banco de dados no local especificado em db/database.yml

$ rake db:drop # deleta o banco de dados no local especificado em db/database.yml

Algo mudou na pasta db/

2º: Estruturar as tabelas

O mundo mágico das migrações


# para criar uma migração, precisamos de um 'timestamp'.
# Felizmente, o rake tem uma task para isso.
$ TS = `rake db:timestamp`
$ touch db/migrate/${TS}_create_tasks.rb

# db/migrate/???????_create_tasks.rb

# estrutura básica de uma migração
# note como o nome da classe bate com o nome do arquivo
class CreateTasks < ActiveRecord::Migration[5.1]
    def change
        # nosso código aqui
    end
end

2º: Estruturar as tabelas

Como criar uma tabela


# db/migrate/???????_create_tasks.rb

# estrutura básica de uma migração
# note como o nome da classe bate com o nome do arquivo
class CreateTasks < ActiveRecord::Migration[5.1]
    def change
        create_table :tasks do |t|
            t.string     :desc
            t.boolean    :done, default: false
            t.timestamps # não é obrigatório.
                         # Guarda quando o objeto foi criado e atualizado
        end
    end
end

3º: modificar o BD

Porque a migração ainda não foi executada


# deixe uma task do rake cuidar disso
$ rake db:migrate # roda todas as migrações posteriores à última executada

Bônus: Novas colunas


# db/migrate/???????_add_deadline_to_tasks.rb

# note como o nome da classe bate com o nome do arquivo
class AddDeadlineToTasks < ActiveRecord::Migration[5.1]
    def change
        add_column :tasks, :deadline, :timestamp, default: Time.now + 2.hours
    end
end

Bônus: Removendo colunas


# db/migrate/???????_remove_deadline_from_tasks.rb

# note como o nome da classe bate com o nome do arquivo
class RemoveDeadlineFromTasks < ActiveRecord::Migration[5.1]
    def change
        remove_column :tasks, :deadline
    end
end

Remover tabela? Alterar tipos? Índices e FKs???

Docs for the win!

(Contém spoilers de Rails)

Brincando com o BD

Lembra do modelo da última aula?


class Task
  attr_reader :id,:desc,:done
  attr_writer :desc

  def initialize(args)
    @id = args['id']
    @desc = args['desc']
    @done = args['done']
  end
end

Esse é ele agora

class Task < ActiveRecord::Base

end

Acesse o console da nossa aplicação

Por meio dele, podemos programar como se estivéssemos no irb, mas com o projeto carregado


$ rake console

Criando e manipulando objetos


task = Task.new
# => #<Task:0x00007feb74ef47c0
# id: nil,
# desc: nil,
# done: false,                      # nosso default
# created_at: nil,
# updated_at: nil>

task.desc = 'Manjar de AR'
# => #<Task:0x00007feb74ef47c0
# id: nil,
# desc: 'Manjar de AR',
# done: false,
# created_at: nil,
# updated_at: nil>

task.save # Salva o modelo no banco de dados, tabela de mesmo nome do objeto

task
# => #<Task:0x00007feb74ef47c0
# id: 1,                            gerado pelo BD (NÃO MEXE NISSO)
# desc: 'Manjar de AR',
# done: false,
# created_at: ?????,                gerado pelo BD
# updated_at: ?????>                gerado pelo BD

Buscando objetos no BD


# => SELECT * FROM tasks WHERE id = 1;
task = Task.find(1)
task
# => #<Task:???? id: 1, desc: 'Manjar de AR', done: false, ... >

# => SELECT * FROM tasks;
tasks = Task.all
tasks.first
# => #<Task:???? id: 1, desc: 'Manjar de AR', done: false, ... >

# => SELECT desc FROM tasks;
tasks = Task.all.pluck(:desc)

# => SELECT * FROM tasks WHERE done = true;
tasks = Task.where(done: true)

E se tentarmos salvar uma Task sem descrição?


Task.new.save

Queremos que isso falhe, pois uma Task sem descrição é inútil...

Validações

Feitas dentro do modelo sobre o atributo que queremos validar

class Task < ActiveRecord::Base

    valida a presença da descrição
    validates :desc, presence: true

end

Se tentarmos salvar uma Task sem descrição agora...


Task.new.save # => erro

Vários tipos de validação


# presence
# uniqueness
# inclusion
# length
# numericality

Assossiações

Como conectar tabelas / modelos

O que queremos?

  • Um objeto chamado Owner
    • Cada task tem um owner
    • Um owner tem várias tasks

Crie a migração e o modelo


# db/migrate/???????_create_owners.rb

class CreateOwners < ActiveRecord::Migration[5.1]
    def change
        create_table :tasks do |t|
            t.string     :nome
            t.timestamps 
        end
    end
end

# app/models/owner.rb
class Owner < ActiveRecord::Base

end

Não esqueça de rodar a migração

Quem vai referenciar quem?

  • 1 owner tem N tasks
  • 1 task tem 1 owner

O task vai referenciar o owner

Como referenciar?

Uma nova migration


# db/migrate/???????_add_owner_to_tasks.rb

# note como o nome da classe bate com o nome do arquivo
class AddDeadlineToTasks < ActiveRecord::Migration[5.1]
    def change
        # add_reference, não add_column
        
        add_reference :tasks, :owner, foreing_key: true
    end
end
Made with Slides.com