Ruby on Rails Fullstack

Model - View - Controller

A arquitetura MVC

Model

  • Models são as classes que utilizamos no nosso projeto
  • Numa arquitetura MVC, tudo gira em torno dos models, suas propriedades e a manipulação dos mesmos
  • Em geral, cada tabela do BD é um Model, mas podemos ter outros models também (para encapsulamento de código)

View

  • Views são arquivos que guardarão todo o código o qual o usuário possa interagir (interfaces)
  • Views não realizam operações complexas! Elas apenas:
    • Recebem input do usuário
    • Mostram algo na tela
  • Views não:
    • Fazem consultas no BD
    • Criam novas instâncias de modelos

Controller

  • O Big Boss. O Master of Puppets. The Manager. Key master. Bam bam bam...
  • Decide qual view deve aparecer e o que ela deve mostrar / recuperar
  • Cria novos modelos, faz manipulação dos mesmos e interage com o BD
  • Essa decisão é baseada num sinal recebido...

Bônus: Router

  • Chefe do Boss (controller)
  • Em termos simples, é o menu da aplicação
    • Mostra o que o usuário pode fazer a seguir
    • Recebe a opção do usuário
    • Fala pro controller correspondente agir, baseado na ação escolhida pelo usuário
    • Uma vez que o controller termina sua ação, o Router refaz esses passos até o usuário decidir finalizar a aplicação

Bônus: App

  • O Universo
  • Encapsula toda a aplicação
    • Inicializa o Banco de Dados
    • Inicializa Controllers e Routers
    • Inicializa o loop do Router

Ok, let's code that

Vamos fazer um Task Manager

1 - Estrutura da Aplicação

  • lib/
    • models/
    • views/
    • db/
    • controller/
    • app.rb
    • router.rb

2 - app.rb


require 'sqlite3' # precisamos disso para usar os comandos SQLite

require_relative 'router'

# inicializamos a constante que irá referenciar nosso BD
db_path = File.join(File.dirname(__FILE__), "db/tasks.sqlite")
DB = SQLite3::Database.new(db_path)


# inicia uma instacia de router e põe pra rodar
router = Router.new
router.run

3 - router.rb

class Router

  # inicializamos o router colocando uma variável para controlar o loop
  def initialize
    @running = true
  end

  # método do loop, para mostrar as opções, pegar o input e delegar uma
  # ação ao controller
  def run
    puts "============================"
    puts "======  TASK MANAGER  ======"
    puts "============================"

    while @running
      show_options
      opt = gets.chomp.to_i
      print `clear`
      run_option(opt)
    end
  end

  # método para mostrar as opções (chamado pelo run)
  def show_options
    puts 'What would you like to do?'
    puts '1 - ?????'
    puts '2 - ?????'
    puts '3 - ?????'
    puts '4 - ?????'
    puts '5 - ?????'
    puts '0 - exit the task manager'
  end

  # método para delegar uma opção ao controller ou parar o app
  def run_option(opt)
    case opt
    when 0
      puts 'See you later!'
      @running = false
    else
      puts 'Invalid option...'
    end
  end

end

4 - models

# como faremos um TASK manager, um modelo interessante seria o próprio task
# Para simplicidade, teremos apenas seu Id, a descrição da task e um boolean
# indicando se a task foi realizada ou não

# lib/models/task.rb
class Task
    # não queremos que ações externas alterem o id ou se foi feito
    attr_reader :id, :done
    
    # queremos poder alterar a descrição da task
    attr_accessor :desc

    def initialize(attrs = {})
        @id = attrs['id']
        @desc = attrs['desc']
        @done = attrs['done'] || false
    end
    
    # queremos ter um método para marcar a task como feita
    def mark_as_done!
        @done = true
    end
end

5 - controllers

# precisamos de um controller para as ações em cima de uma task. Damos a esse
# o nome de TaskController.

# lib/controllers/task_controller.rb

# Como iremos utilizar o modelo, precisamos 'importá-lo'
require_relative '../models/task'

class TaskController

    # ação para listar todas as tasks que já temos
    def list_all
        # 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

        # 3. Mostre-o na tela para o usuário
        ???
    end

end

6 - views

# precisamos de uma view para interagir com o usuário (dependendo da ação 
# do controller)

# lib/views/task_view.rb

class TaskView

    # ação para listar todas as tasks que já temos
    # essa ação espera receber uma array de tasks (para mostrá-las na tela)
    def show_all(tasks)
        puts '====== Your tasks ======='
        tasks.each do |task|
          done = task.done ? '[X]' : '[ ]'
          puts "#{task.id}: #{done} #{task.desc}"
        end
        puts '========================='
    end
end

7 - conectando controller - view

# para chamarmos o método da view no controller, precisamos 'importá-lo'

# lib/controllers/task_controller.rb
require_relative '../models/task'
require_relative '../views/task_view' # importando a view

class TaskController

    # para não termos que criar uma View toda vez que formos usar
    # criamos uma variável de instância que utilizaremos toda hora
    def initialize
        @view = TaskView.new
    end

    # ação para listar todas as tasks que já temos
    def list_all
        # ...

        # 3. Mostre-o na tela para o usuário (passando as tasks geradas para
        # a view
        @view.show_tasks(tasks)
    end

end

8 - conectando router - controller

# para delegarmos ações ao controller, precisamos chamá-lo aqui no router
# para chamarmos o controller, precisamos 'importá-lo'

# lib/router.rb
require_relative 'controllers/task_controller'

class Router

  # inicializamos o router colocando uma variável para controlar o loop
  # e outra para guardar o controller o qual delegaremos ações baseados
  # na ação do usuário
  def initialize
    @controller = TaskController.new # criando um controller pra usar
    @running = true
  end

  # ...

  # método para mostrar as opções (chamado pelo run)
  def show_options
    puts 'What would you like to do?'
    puts '1 - List all my tasks'        # nova opção liberada!!
    puts '2 - ?????'
    puts '3 - ?????'
    puts '4 - ?????'
    puts '5 - ?????'
    puts '0 - exit the task manager'
  end

  # método para delegar uma opção ao controller ou parar o app
  def run_option(opt)
    case opt
    when 0
      puts 'See you later!'
      @running = false
    when 1
        @controller.list_all            # fala pro controller o que fazer
    else
      puts 'Invalid option...'
    end
  end

end

9 - O app.rb inicializa os controllers

# Para refatoração, os controllers são criados no app.rb e então
# passados (como parâmetro) para o router

require 'sqlite3' # precisamos disso para usar os comandos SQLite

require_relative 'router'
require_relative 'controllers/task_controller' # precisamos 'importá-lo'

# inicializamos a constante que irá referenciar nosso BD
db_path = File.join(File.dirname(__FILE__), "db/tasks.sqlite")
DB = SQLite3::Database.new(db_path)

# inicia o controller
task_controller = TaskController.new

# inicia uma instacia de router e põe pra rodar
router = Router.new(task_controller) # passamos o task_controller pro router
router.run

10 - atualizando o initialize do router

# lib/router.rb
require_relative 'controllers/task_controller'

class Router

  # inicializamos o router colocando uma variável para controlar o loop
  # e outra para guardar o controller o qual delegaremos ações baseados
  # na ação do usuário
  def initialize(controller) # esperamos receber os controllers
    @controller = controller # instanciamos o controller
    @running = true
  end

  # ...

end

Exercício

Complete o Task Manager! Existem mais quatro coisas que podemos fazer na aplicação:

  • Criar uma nova task
  • Atualizar a descrição de uma task
  • Marcar uma task como feita
  • Deletar uma task qualquer

Pense em como saber interagir com o usuário (view), tanto para mostrar quanto para receber informação.

Pense em como interagir com o Banco de Dados (controller), manipulando modelos e salvando as alterações, inserções e remoções  (dica: BD.execute() )

AC - Ruby On Rails - Aula 04 - parte 2

By Rafael Pereira Alonso

AC - Ruby On Rails - Aula 04 - parte 2

Quarta Aula (parte 2) da Atividade Complementar de Ruby On Rails, lecionada na Universidade Federal de São Carlos, campus Sorocaba, no primeiro semestre de 2019

  • 30