Transcript

RUBY ON RAILSMAURÍCIO EDUARDO LOSCHI BATISTA

RUBY

● Interpretada

● Dinâmica

● Multiparadigma

HISTÓRIA

● Criada por Yukihiro “Matz” Matsumoto em Fev/1993.

● Primeira release pública (v0.95) em Dez/1995

● Primeira versão estável em Dez/1996

AS INSPIRAÇÕES DE MATZ

● Perl

● Smalltalk

● Eiffel

● Ada

● Lisp

RUBY POR MATZ

“Ruby is simple in appearance, but is very complex inside, just like our human body.”

Matz -

$ irb

LITERAIS

1 # número inteiro'a' # string:a # símbolo[1, 'a'] # array{a: 1, 'a' => 2} # hash0..9 # range inclusivo0...10 # range exclusivotrue # booleano verdadeirofalse # booleano falsonil # nulo

ESCOPO DE VARIÁVEIS

v = 1 # escopo local@v = 2 # escopo da instância@@v = 3 # escopo da classe$v = 4 # escopo globalprint v, @v, @@v, $v # 1234

CONSTANTES

NO = 2 # tudo que se inicia com letra maiúscula é uma constanteNO = 3 # warning: already initialized constant NO # warning: previous definition of NO was here

MÉTODOS

s = 'abc's.reverse # => 'cba's # => 'abc's.reverse! # => 'cba's # => 'cba's.include? 'a' # => true

MÉTODOS E PARÂMETROS

def splat(*args) p argsend

def keywords(x:, y: 1) print x, yend

splat 1, 2, 3 # [1, 2, 3]keywords x: 2 # 21

MÉTODOS MENSAGENS

'abc'.reverse # => "cba"'abc'.send :reverse # => "cba"

to_*

10.to_f # => 10.010.to_s # => "10"'10'.to_i # => 10'ab'.to_sym # => :ab(1..5).to_a # => [1, 2, 3, 4, 5]

ESTRUTURAS DE CONTROLE

def m(x) if x > 0 puts x elsif x == 0 puts "zero" else puts "-" endend

m 1 # +m 0 # zerom -1 # -

def u(x) unless x.nil? puts x endend

u 1 # 1u nil

x = 0while x < 5 print x x += 1end # 01234

for x in 0..4 print xend # 01234

x = 0until x == 5 print x x += 1end # 01234

MODIFICADORES

a = []a.push(a.length) while a.length < 3a.push(a.length) until a.length == 5p a if a.length > 4 # [0, 1, 2, 3, 4]p a unless a.length < 5 # [0, 1, 2, 3, 4]

BLOCOS

[1, 2, 3].map { |i| i**2 } # => [1, 4, 9]

[1, 2, 3].each do |i| puts i puts i**2end

BLOCOS E ARGUMENTOS

def block_yield yield 'BLOCO'end

def block_call(&b) b.call 'BLOCO'end

block_yield { |s| puts s.downcase } # blocoblock_call { |s| puts s.reverse } # OCOLB

FOR MAIS RUBISTA

for x in 0..4 print xend

(0..4).each { |x| print x} # 01234

FILOSOFIA DO RUBY

● Felicidade do programador

● Orientação à objeto

● Flexibilidade

Ruby lhe permite programar de forma eficiente e divertida

TUDO É UM OBJETO

“I wanted a scripting language that was more powerful than Perl, and more object-oriented

than Python.”Matz -

MODELO DE OBJETOS DO RUBY

class Pessoa attr_accessor :nome, :idade def to_s "Nome: #{nome}; Idade: #{idade}" endend

eu = Pessoa.neweu.nome = "Eu"eu.idade=(101)puts eu # Nome: Eu; Idade: 101

class Pessoa attr_accessor :nome, :idade def to_s "Nome: #{nome}; Idade: #{idade}" endend

eu = Pessoa.neweu.nome = "Eu"eu.idade=(101)puts eu # Nome: Eu; Idade: 101

self.nome self.idade

MODULE

module M def self.m(x) puts x endend

M.m 1 # 1

MODULE & NAMESPACE

module M class C def m(x) puts x end endend

M::C.new.m 1 # 1

MODULE & MIXIN

module M def m(x) puts x endend

class C include Mend

C.new.m 1 # 1

TODAS as classes podem ser modificadas a qualquer momento (também conhecido como monkeypatch)

CLASSES ABERTAS

CONTANTO ALGARISMOS DE UM NÚMERO

10.length #NoMethodError: undefined method `length' for 10:Fixnum

class Fixnum def length to_s.length endend

10.length # => 2

MÉTODOS FANTASMAS

class C def method_missing(meth, *args) puts "#{meth} chamado com #{args}" yield args if block_given? endend

o = C.newz = o.matematica(5, 2) { |x, y| x * y } # matematica chamado com [5, 2]puts z # 10

● Linguagem dedicada a um domínio de problema específico○ HTML, CSS, SQL (DSLs externas)

● Ruby é uma GPL (linguagem de propósito geral)

○ Permite a criação de DSL’s internas (Capybara, RSpec)

DSL

# rspecdescribe "true or false" do it "should be true" do expect(true || false).to be true endend

# capybaravisit '/'fill_in 'e-mail', with: '[email protected]'click_on 'Ok'

ECOSSISTEMA

● RubyGems

● Bundler

● Rake

RAILS

● Framework MVC de aplicações web

● Criado em 2003 por David Heinemeier Hansson

● Extraído do Basecamp● + de 3400 contribuidores

FILOSOFIA DO RAILS

● DRY: não se repita

● COC: convenção sobre configuração

● Tem o mesmo propósito do ruby: fazer os programadores felizes

MVC ORIGINAL

Banco de Dados

Rails routes

ActionController

ActionView ActiveRecord

1

2

4

5

6

7

8 3

1. navegador envia a requisição2. roteamento encontra o controller3. controller requisita o model4. model requisita os dados5. BD retorna os dados6. model retorna7. controller requisita a renderização8. view retorna a página9. controller responde a requisição

HTTP com a página

9

ARQUITETURA

$ rails new notas

$ cd notas

DIRETÓRIO app● assets: js, css, imagens, etc.● controllers: controladores do MVC

○ application_controller.rb: controller geral da aplicação

● helpers: métodos auxiliares para controllers e views● mailers: classes para enviar e-mails● models: modelos do MVC

○ concerns: comportamentos compartilhados por modelos

● views: Visões do MVC○ layouts: layouts para os templates

DIRETÓRIO testTestes da classes da aplicação

● fixtures: dados para os testes

● integration: testes de integração de todos os componentes da aplicação

DIRETÓRIO config● environments: configurações exclusivas para cada

ambiente de execução○ Rails provê 3 por padrão: development, test e production

● initializers: rotinas de inicialização● locales: traduções● application.rb: configurações comuns para todos os

ambientes● database.yml: configurações do banco de dados● routes.rb: roteamento do rails

OUTROS DIRETÓRIOS● bin: wrappers de executáveis● db: rotinas relacionadas à persistencia

○ migrate: migrações de BDs relacionais● lib: rotinas não específicas da aplicação

○ assets: assets não específicos da aplicação○ tasks: tarefas personalizadas do rake

● log: logs da aplicação● public: arquivos servidos diretamente pelo servidor

web● tmp: arquivos temporários● vendor: rotinas criadas por terceiros

○ assets: assets criados por terceiros

Gemfile

gem 'rails-i18n', github: 'svenfuchs/rails-i18n', branch: 'master'

gem 'devise'gem 'devise-i18n'

gem 'bootstrap-sass'gem 'autoprefixer-rails'

gem 'mailcatcher', group: :development

$ bundle

$ rails g devise:install

config/application.rb

config.i18n.default_locale = :"pt-BR"

config/environments/development.rb

config.action_mailer.default_url_options = {host: 'localhost'}config.action_mailer.delivery_method = :smtpconfig.action_mailer.smtp_settings = {address: 'localhost', port: 1025}

$ mailcatcher

$ rails s

$ rails g devise User

$ rake db:migrate

YAML

● Padrão amigável de serialização de dados

● Baseada em indentação

test/fixtures/users.yml

eu: email: [email protected] encrypted_password: "..."

voce: email: [email protected] encrypted_password: "..."

$ rails c> User.new(password: "12345678").encrypted_password => …

Desenvolvimento Orientado a Comportamento● Outside-in

● Testa-se o comportamento, não a implementação

BDD

Gemfile

gem 'capybara', group: [:development, :test]gem 'capybara-webkit', group: [:development, :test]

test/test_helper.rb

# …require 'minitest/mock'require 'capybara/rails'# …class ActionDispatch::IntegrationTest include Capybara::DSLend# …class ActionController::TestCase include Devise::TestHelpersend

$ bundle

$ rails g integration_test notes_listing

TESTES DE INTEGRAÇÃO (ACEITAÇÃO)

● Testes de alto nível (caixa preta)

● Simulam a interação do usuário

● Baseados em cenários

test/integration/notes_listing_test.rb (continua...)

class NotesListingTest < ActionDispatch::IntegrationTest setup do visit '/users/sign_in' fill_in 'Email', with: users(:eu).email fill_in 'Password', with: '12345678' click_on 'Log in' end

teardown do Capybara.reset_sessions! end

test/integration/notes_listing_test.rb (continua...)

test 'listagem de notas' do visit '/' assert page.has_content? notes(:one).title assert page.has_content? notes(:one).body assert page.has_no_content? notes(:two).title assert page.has_no_content? notes(:two).body end

test/integration/notes_listing_test.rb

test 'cor das notas' do visit '/' assert page.all('.note .well').first['style'] .include? "background-color: #{notes(:one).color}" endend

$ rake test

config/routes.rb

Rails.application.routes.draw do resources :notes, path: '/' # ...

$ rails g controller notes

TESTES FUNCIONAIS

● Testa o resultado de uma funcionalidade

● Efeitos colaterais e resultados intermediários não importam

test/controllers/notes_controller_test.rb

class NotesControllerTest < ActionController::TestCase test 'index' do get :index assert_response :success assert_not_nil assigns(:notes) endend

app/controllers/notes_controller.rb

class NotesController < ApplicationController def index @notes = Note.all endend

$ rails g model Note title:string \ body:text color:string user:references

$ rake db:migrate

test/fixtures/notes.yml (continua)

one: title: Nota1 Busca body: Texto1 color: Gold user: eu

two: title: Nota2 body: Texto2 color: Gold user: voce

test/fixtures/notes.yml

three: title: Nota3 body: Texto3 Busca color: Gold user: eu

four: title: Nota4 body: Texto4 color: Gold user: voce

● Testam as menores unidades de funcionalidade

● Não deve haver interação com outros componentes

TESTES UNITÁRIOS

test/models/note_test.rb

class NoteTest < ActiveSupport::TestCase test "deve ter texto" do n = notes(:one) n.body = nil assert_not n.save end

test "deve ter uma cor válida" do n = notes(:one) n.color = 'Black' assert_not n.save endend

app/models/note.rb

class Note < ActiveRecord::Base @allowed_colors = %w(Gold LightGreen Pink SkyBlue) # … validates :body, presence: true validates :color, inclusion: { in: @allowed_colors }end

$ rm app/assets/stylesheets/application.css

$ touch app/assets/stylesheets/application.css.scss

ASSETS PIPELINE

● Framework para pré-processar, concatenar e minificar JS e CSS

● Reduz a quantidade e o tamanho das requisições

SASS

● Linguagem de script que adiciona funcionalidades ao CSS

● “CSS com superpoderes”

● CSS válido é SCSS válido

app/assets/stylesheets/application.css.scss

body { padding-top: 70px; }

@import "bootstrap-sprockets";@import "bootstrap";@import "notes"

app/assets/javascripts/application.js

// ...//= require turbolinks//= require bootstrap-sprockets// ...

● Embedded Ruby (Ruby Incorporado)

● Processa código ruby e gera HTML

TEMPLATES & ERB

app/views/notes/index.html.erb

<div class="row notes"> <% @notes.each do |note| %> <div class="note"> <div class="well well-sm" style="background-color: <%= note.color %>"> <h4> <strong><%= note.title %></strong> </h4> <p><%= simple_format note.body %></p> </div> </div> <% end %></div>

● Template para definir estruturas comuns a outros templates

LAYOUTS

app/views/layouts/application.html.erb (continua)

<!DOCTYPE html><!-- ... --> <title>Notas</title> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> <!-- ... --><body> <% if user_signed_in? %> <!-- ... --> </button> <%= link_to 'Notas', notes_path , class: "navbar-brand" %> <!-- ... -->

app/views/layouts/application.html.erb (continua)

<ul class="nav navbar-nav navbar-right"> <li><%= link_to t('actions.log_out'), destroy_user_session_path, method: :delete %></li> <!-- ... --></nav><% end %>

app/views/layouts/application.html.erb

<div class="container"> <% if flash[:notice] %> <!-- ... --> </button> <%= simple_format flash[:notice] %> </div> <% end %> <% if flash[:alert] %> <!-- ... → </button> <%= simple_format flash[:alert] %> </div> <% end %> <%= yield %> <!-- ... --></html>

● Traduções e formatação de números, datas, etc.

INTERNACIONALIZAÇÃO

config/locales/pt-BR.yml

pt-BR: actions: log_out: Sair

$ rake db:fixtures:load

$ rails s

test/controllers/notes_controller_test.rb

class NotesControllerTest < ActionController::TestCase setup do sign_in users(:eu) end # ... test 'index deve mostrar somente as notas do usuário atual' do get :index assert_not assigns(:notes).include? notes(:two) endend

app/controllers/notes_controller.rb

class NotesController < ApplicationController before_action :authenticate_user!

def index @notes = Note.where(user: current_user) endend

test/controllers/notes_controller_test.rb

class NotesControllerTest < ActionController::TestCase # ... test 'index deve retornar a nota mais recente primeiro' do note = Note.last note.touch note.save get :index assert assigns(:notes).first .updated_at > assigns(:notes).last.updated_at endend

app/controllers/notes_controller.rb

class NotesController < ApplicationController # ... def index @notes = Note.where(user: current_user) .order(updated_at: :desc) endend

$ rails g integration_test note_creation

test/test_helper.rb (continua)

# ...module JSHelper def use_js Capybara.current_driver = :webkit @js = true end

def teardown super if @js Capybara.use_default_driver @js = false end endend

test/test_helper.rb (continua)

module SessionHelper def log_in visit '/users/sign_in' fill_in 'Email', with: users(:eu).email fill_in 'Password', with: '12345678' click_on 'Log in' end

def teardown super Capybara.reset_sessions! endend

test/test_helper.rb (continua)

# ...class ActionDispatch::IntegrationTest # ... include JSHelper include SessionHelperend

test/test_helper.rb

# ...class ActiveRecord::Base mattr_accessor :shared_connection @@shared_connection = nil

def self.connection @@shared_connection || retrieve_connection endendActiveRecord::Base.shared_connection = ActiveRecord::Base.connection

test/integration/notes_listing_test.rb

class NotesListingTest < ActionDispatch::IntegrationTest setup do log_in end # ...

test/integration/note_creation_test.rb (continua)

class NoteCreationTest < ActionDispatch::IntegrationTest test 'criação de nota' do use_js log_in visit '/' click_on I18n.t('actions.create_note') fill_in 'note_title', with: 'Nova Nota' fill_in 'note_body', with: 'Novo Texto' click_on 'SkyBlue' click_on I18n.t('actions.save_note') assert page.has_content?('Nova Nota'), 'Título não encontrado' assert page.has_content?('Novo Texto'), 'Texto não encontrado' assert page.all('.note .well').first['style'] .include?("background-color: SkyBlue"), "Cor errada" end

test/integration/note_creation_test.rb

test 'erro na criação de nota' do log_in visit '/' click_on I18n.t('actions.create_note') click_on I18n.t('actions.save_note') assert page.has_selector? 'div#errors' endend

app/views/layouts/application.html.erb

<!-- ... --><div id="navbar" class="navbar-collapse collapse"> <%= link_to t('actions.create_note'), new_note_path, class: "btn btn-success navbar-btn navbar-left" %><!-- ... -->

test/controllers/notes_controller_test.rb

# ... test 'new' do get :new assert_response :success assert_not_nil assigns(:note) end

test 'new deve instanciar uma nova nota' do get :new assert_not assigns(:note).persisted? end

test 'nova nota deve ser dourada por padrão' do get :new assert 'Gold', assigns(:note).color endend

app/controllers/notes_controller.rb

class NotesController < ApplicationController # ... def new @note = Note.new(color: 'Gold') endend

● Helpers para criação de formulários

● Facilitam a criação de formulários para ações em modelos

FORM HELPERS

app/views/notes/new.html.erb (continua)

<%= form_for @note, html: {role: 'form'} do |n| %> <% if @note.errors.any? %> <!-- ... --> </button> <strong> <%= t('errors.save_note', count: @note.errors.count) %> </strong> <ul> <% @note.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <%= n.hidden_field :color %>

app/views/notes/new.html.erb (continua)

<!-- ... --><div id="note-card" class="well" style="background-color: <%= @note.color %>"> <div class="form-group"> <%= n.text_field :title, class: 'form-control input-lg', placeholder: t('placeholders.title') %> </div> <div class="form-group"> <%= n.text_area :body, class: 'form-control', placeholder: t('placeholders.body'), rows: 10 %> <!-- ... --><ul class="list-unstyled"> <% Note.allowed_colors.each do |color| %> <li> <a id="<%= color %>" href="#" class="btn" style="background-color: <%= color %>" onclick="change_color('<%= color %>')"></a> </li> <% end %>

app/views/notes/new.html.erb

<!-- ... --> <div class="col-xs-12"> <%= n.submit t('actions.save_note'), class: 'btn btn-success' %> </div> </div><% end %>

● Metaclasse ou Classe Singleton

● Implícita e exclusiva de cada objeto

EIGENCLASS

class C; end

obj = C.newdef obj.hey puts 'hey'end# hey está definido na eigenclass de obj

obj.hey # heyC.new.hey # NoMethodError: undefined method `hey' for #<C:0x...>

app/models/note.rb

class Note < ActiveRecord::Base # ... class << self attr_reader :allowed_colors endend

● Linguagem que compila para JS

● Simplifica o JS

● Inspirado em Ruby, Python e Haskell

COFFEESCRIPT

app/assets/javascripts/notes.js.coffee

@change_color = (color) -> $('#note-card').css 'background-color', color $('input[name="note[color]"]').val color

config/locales/pt-BR.yml

pt-BR: actions: create_note: Criar nota log_out: Sair save_note: Salvar errors: save_note: one: 1 erro other: "%{count} erros" placeholders: body: Texto title: Título

test/controllers/notes_controllers_test.rb (continua)

# ... test 'create' do post :create, note: {title: 'Note', body: 'Text', color: 'Gold'} assert_redirected_to controller: 'notes', action: 'index' end

test 'create deve salvar a nota' do assert_difference('Note.where(user: users(:eu)).count') do post :create, note: {title: 'Note', body: 'Text', color: 'Gold'} end end

test/controllers/notes_controllers_test.rb

test 'create deve renderizar o formulario de criação novamente em caso de erro' do post :create, note: {title: 'Note', color: 'Gold'} assert_template :new end

test 'create deve retornar um erro se a nota não for salva' do post :create, note: {title: 'Note', color: 'Gold'} assert_response :unprocessable_entity endend

● Permite controlar especificamente quais atributos podem ser definidos em massa

STRONG PARAMETERS

app/controllers/notes_controller.rb

class NotesController < ApplicationController # ... def create @note = Note.new(note_params.merge(user: current_user)) if @note.save redirect_to notes_path else render :new, status: :unprocessable_entity end end

private

def note_params params.require(:note).permit(:title, :body, :color) endend

$ rails g integration_test note_editing

test/integration/note_editing_test.rb (continua)

class NoteEditingTest < ActionDispatch::IntegrationTest test 'editação de nota' do use_js log_in visit '/' click_on "actions-#{notes(:one).id}" click_on "edit-#{notes(:one).id}" fill_in 'note_title', with: 'Nova Nota' fill_in 'note_body', with: 'Novo Texto' click_on 'SkyBlue' click_on I18n.t('actions.save_note') assert page.has_content?('Nova Nota'), 'Título não encontrado' assert page.has_content?('Novo Texto'), 'Texto não encontrado' assert page.all('.note .well').first['style'] .include?("background-color: SkyBlue"), "Cor errada" end

test/integration/note_editing_test.rb (continua)

test 'erro na edição de nota' do use_js log_in visit '/' click_on "actions-#{notes(:one).id}" click_on "edit-#{notes(:one).id}" fill_in 'note_body', with: '' click_on I18n.t('actions.save_note') assert page.has_selector? 'div#errors' endend

app/views/notes/index.html.erb

<!-- ... --><div class="well well-sm" style="background-color: <%= note.color %>"> <div class="dropdown pull-right"> <button id="actions-<%= note.id %>" type="button" class="btn btn-link note-actions" data-toggle="dropdown"> <span class="glyphicon glyphicon-cog"></span> </button> <ul class="dropdown-menu"> <li> <%= link_to edit_note_path(note.id), id: "edit-#{note.id}" do %> <span class="glyphicon glyphicon-pencil note-actions note-action"> </span><%= t('actions.edit_note') %> <% end %> </li> </ul> </div>

test/controllers/notes_controller_test.rb

class NotesControllerTest < ActionController::TestCase # ... test 'edit' do get :edit, id: notes(:one).id assert_response :success assert_not_nil assigns(:note) end

test 'edit deve retornar a nota correta para ser editada' do get :edit, id: notes(:one).id assert_equal notes(:one), assigns(:note) end

test 'edit deve disparar uma exceção se a nota não for encontrada' do assert_raises(ActiveRecord::RecordNotFound) { get :edit, id: 1 } endend

app/controllers/notes_controller.rb

class NotesController < ApplicationController # ... def edit @note = Note.find(params[:id]) end # ...

● DRY

● Extração de partes comuns entre templates

PARTIALS

app/views/notes/{new,edit}.html.erb

<%= render 'form' %>

config/locales/pt-BR.yml

pt-BR: actions: create_note: Criar nota edit_note: Editar # ...

test/controllers/notes_controller_test.rb (continua)

class NotesControllerTest < ActionController::TestCase # ... test 'update' do patch :update, id: notes(:one).id, note: {title: 'Update Note', body: 'Update Text', color: 'Pink'} assert_redirected_to controller: 'notes', action: 'index' end

test 'update deve atualizar a nota' do patch :update, id: notes(:one).id, note: {title: 'Update Note', body: 'Update Text', color: 'Pink'} notes(:one).reload assert_equal 'Update Note', notes(:one).title assert_equal 'Update Text', notes(:one).body assert_equal 'Pink', notes(:one).color end

test/controllers/notes_controller_test.rb

test 'update deve renderizar o formulario de edição novamente em caso de erro' do patch :update, id: notes(:one).id, note: {title: 'Update Note', body: '', color: 'Pink'} assert_template :edit end

test 'update deve retornar um erro se a nota não for salva' do patch :update, id: notes(:one).id, note: {title: 'Update Note', body: '', color: 'Pink'} assert_response :unprocessable_entity end

test 'update deve disparar uma exceção se a nota não for encontrada' do assert_raises(ActiveRecord::RecordNotFound) { patch :update, id: 1 } endend

app/controllers/notes_controller.rb

class NotesController < ApplicationController # ... def update @note = Note.find(params[:id]) if @note.update(note_params) redirect_to notes_path else render :edit, status: :unprocessable_entity end end # ...

$ rails g integration_test note_deleting

test/integration/note_deleting_test.rb

class NoteDeletingTest < ActionDispatch::IntegrationTest test 'exclusão de nota' do use_js log_in visit '/' click_on "actions-#{notes(:one).id}" page.accept_confirm do click_on "delete-#{notes(:one).id}" end assert page.has_no_content? notes(:one).title endend

<ul class="dropdown-menu"> <!-- ... --> <li> <%= link_to note_path(note), method: :delete, data: {confirm: t('messages.are_you_sure?')}, id: "delete-#{note.id}" do %> <span class="glyphicon glyphicon-trash note-actions note-action"></span> <%= t('actions.delete_note') %> <% end %> </li></ul> <!-- ... -->

config/locales/pt-BR.yml

pt-BR: actions: create_note: Criar nota delete_note: Excluir # … errors: delete_note: Não foi possível excluir a nota # … messages: are_you_sure?: Tem certeza? # ...

test/controllers/notes_controller_test.rb (continua)

class NotesControllerTest < ActionController::TestCase # ... test 'destroy' do delete :destroy, id: notes(:one).id assert_redirected_to controller: 'notes', action: 'index' end

test 'destroy deve excluir a nota' do assert_difference('Note.count', -1) { delete :destroy, id: notes(:one).id } end

test 'destroy deve disparar uma exceção se a nota não for encontrada' do assert_raises(ActiveRecord::RecordNotFound) { delete :destroy, id: 1 } end

● Mocks: simulam o comportamento de objetos reais

● Stubs: simulam a execução de métodos reais

MOCKS & STUBS

test/controllers/notes_controller_test.rb

test 'destroy deve colocar as mensagens de erro no flash alert' do note = MiniTest::Mock.new errors = MiniTest::Mock.new note.expect :id, notes(:one).id note.expect :destroy, false note.expect :errors, errors errors.expect :full_messages, ['error'] Note.stub :find, note do delete :destroy, id: notes(:one).id assert_equal I18n.t('errors.delete_note'), flash[:alert] assert_response :unprocessable_entity end endend

app/controllers/notes_controller.rb

class NotesController < ApplicationController # ... def destroy @note = Note.find(params[:id]) if @note.destroy redirect_to notes_path else flash[:alert] = I18n.t('errors.delete_note') redirect_to notes_path, status: :unprocessable_entity end end # ...

$ rails g integration_test notes_searching

test/integration/notes_searching_test.rb (continua)

class NotesSearchingTest < ActionDispatch::IntegrationTest setup do log_in end

test 'buscando por título exato' do visit '/' fill_in 'query', with: notes(:one).title click_on 'search' assert page.has_content? notes(:one).body assert page.has_no_content? notes(:three).title assert page.has_content? I18n.t('messages.n_notes_found', count: 1) end

test/integration/notes_searching_test.rb (continua)

test 'buscando por texto exato' do visit '/' fill_in 'query', with: notes(:one).body click_on 'search' assert page.has_content? notes(:one).title assert page.has_no_content? notes(:three).title assert page.has_content? I18n.t('messages.n_notes_found', count: 1) end

test 'buscando por título parcial' do visit '/' fill_in 'query', with: notes(:one).title[0..2] click_on 'search' assert page.has_content? notes(:one).title assert page.has_content? notes(:three).title assert page.has_content? I18n.t('messages.n_notes_found', count: 2) end

test/integration/notes_searching_test.rb (continua)

test 'buscando por texto parcial' do visit '/' fill_in 'query', with: notes(:one).body[0..2] click_on 'search' assert page.has_content? notes(:one).title assert page.has_content? notes(:three).title assert page.has_content? I18n.t('messages.n_notes_found', count: 2) end

test 'resultados no título e no texto de notas diferentes' do visit '/' fill_in 'query', with: 'Busca' click_on 'search' assert page.has_content? notes(:one).title assert page.has_content? notes(:three).title assert page.has_content? I18n.t('messages.n_notes_found', count: 2) end

app/views/layouts/application.erb.html

<!-- ... --><%= link_to t('actions.create_note'), new_note_path, class: "btn btn-success navbar-btn navbar-left" %><%= form_tag search_notes_path, method: :get, class: 'navbar-form navbar-left', role: 'search' do %> <div class="input-group"> <%= text_field_tag :query, params[:query], class: 'form-control', placeholder: t('placeholders.search_notes') %> <span class="input-group-btn"> <%= button_tag id: 'search', type: 'submit', class: 'btn btn-primary', name: nil do %> <span class="glyphicon glyphicon-search"></span> <% end %> </span> </div><% end %><!-- ... -->

test/integration/notes_searching_test.rb

test 'nenhum resultado' do visit '/' fill_in 'query', with: 'abcd' click_on 'search' assert page.has_content? I18n.t('messages.no_notes_found') assert page.has_no_content? notes(:one).title assert page.has_no_content? notes(:three).title endend

config/routes.rb

Rails.application.routes.draw do resources :notes, path: '/' do get 'search' => 'notes#search', on: :collection, as: :search end # ...

test/controllers/notes_controllers_test.rb (continua)

class NotesControllerTest < ActionController::TestCase # ... test 'search' do get :search, query: notes(:one).title assert_response :success assert_not_nil assigns(:notes) end

test 'search deve buscar somente as notas do usuario atual' do get :search, query: 'N' assert_equal 2, assigns(:notes).count end

test 'search deve buscar pelo titulo completo' do get :search, query: notes(:one).title assert_equal notes(:one), assigns(:notes).first end

test/controllers/notes_controllers_test.rb (continua)

test 'search deve buscar pelo texto completo' do get :search, query: notes(:one).body assert_equal notes(:one), assigns(:notes).first end

test 'search deve buscar pelo titulo parcial' do get :search, query: notes(:one).title[0..2] assert assigns(:notes).include? notes(:one) assert assigns(:notes).include? notes(:three) end

test 'search deve buscar pelo texto parcial' do get :search, query: notes(:one).body[0..2] assert assigns(:notes).include? notes(:one) assert assigns(:notes).include? notes(:three) end

test/controllers/notes_controllers_test.rb

test 'search deve retornar resultados de título e texto em notas diferentes' do get :search, query: 'Busca' assert assigns(:notes).include? notes(:one) assert assigns(:notes).include? notes(:three) end

test 'search deve colocar a quantidade de resultados no flash notice' do get :search, query: notes(:one).title assert_equal I18n.t('messages.n_notes_found', count: 1), flash[:notice] end

test 'search deve colocar a mensagem de nenhum resultados no flash alert' do get :search, query: 'abcd' assert_equal I18n.t('messages.no_notes_found'), flash[:alert] end

app/controllers/notes_controller.rb

class NotesController < ApplicationController # ... def search @notes = Note.where(user: current_user) .where(' notes.title LIKE ? OR notes.body LIKE ?', "%#{params[:query]}%", "%#{params[:query]}%") .order(updated_at: :desc) if @notes.count > 0 flash.now[:notice] = I18n.t('messages.n_notes_found', count: @notes.count) else flash.now[:alert] = I18n.t('messages.no_notes_found') end end # ...

app/views/notes/index.html.erb

<%= render 'list' %>

app/views/notes/search.html.erb

<%= render 'list' %>

config/locales/pt-BR.yml

pt-BR: # ... messages: are_you_sure?: Tem certeza? n_notes_found: one: 1 nota encontrada other: "%{count} notas encontradas" no_notes_found: Nenhuma nota encontrada :-( placeholders: body: Texto search_notes: Pesquisar nas notas # ...

REFERÊNCIASPERROTTA, Paolo. Metaprogramming Ruby. 1 ed. The Pragmatic Bookshelf, 2010.MATSUMOTO, Yukihiro. Ruby in a Nutshell. 1 ed. O’Reilly, 2001.THOMAS, Dave; FOWLER, Chad; HUNT, Andy. Programming Ruby 1.9 & 2.0: The Pragmatic Programmers Guide. 4 ed. The Pragmatic Bookshelf, 2013.BLACK, David A.. The Well-Grounded Rubyist. 1 ed. Manning, 2009.

REFERÊNCIASAbout Ruby - https://www.ruby-lang.org/en/about/The Philosophy of Ruby - http://www.artima.com/intv/rubyP.htmlRubyConf: History of Ruby - http://blog.nicksieger.com/articles/2006/10/20/rubyconf-history-of-ruby/Ruby on Rails Guides - http://guides.rubyonrails.org/


Top Related