Minicurso Ruby on Rails

466 visualizações

Publicada em

Minicurso de Ruby on Rails que ocorreu de 25/11/2014 até 28/11/2014 no Instituto Federal de Educação Ciência e Tecnologia do Sudeste de Minas Campus Barbacena

Publicada em: Software
0 comentários
2 gostaram
Estatísticas
Notas
  • Seja o primeiro a comentar

Sem downloads
Visualizações
Visualizações totais
466
No SlideShare
0
A partir de incorporações
0
Número de incorporações
5
Ações
Compartilhamentos
0
Downloads
21
Comentários
0
Gostaram
2
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

Minicurso Ruby on Rails

  1. 1. RUBY ON RAILS MAURÍCIO EDUARDO LOSCHI BATISTA
  2. 2. RUBY ● Interpretada ● Dinâmica ● Multiparadigma
  3. 3. 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
  4. 4. AS INSPIRAÇÕES DE MATZ ● Perl ● Smalltalk ● Eiffel ● Ada ● Lisp
  5. 5. RUBY POR MATZ “Ruby is simple in appearance, but is very complex inside, just like our human body.” Matz -
  6. 6. $ irb
  7. 7. 1 # número inteiro 'a' # string :a # símbolo [1, 'a'] # array {a: 1, 'a' => 2} # hash 0..9 # range inclusivo 0...10 # range exclusivo true # booleano verdadeiro false # booleano falso nil # nulo LITERAIS
  8. 8. v = 1 # escopo local @v = 2 # escopo da instância @@v = 3 # escopo da classe $v = 4 # escopo global print v, @v, @@v, $v # 1234 ESCOPO DE VARIÁVEIS
  9. 9. NO = 2 # tudo que se inicia com letra maiúscula é uma constante NO = 3 # warning: already initialized constant NO # warning: previous definition of NO was here CONSTANTES
  10. 10. s = 'abc' s.reverse # => 'cba' s # => 'abc' s.reverse! # => 'cba' s # => 'cba' s.include? 'a' # => true MÉTODOS
  11. 11. def splat(*args) p args end def keywords(x:, y: 1) print x, y end splat 1, 2, 3 # [1, 2, 3] keywords x: 2 # 21 MÉTODOS E PARÂMETROS
  12. 12. 'abc'.reverse # => "cba" 'abc'.send :reverse # => "cba" MÉTODOS MENSAGENS
  13. 13. 10.to_f # => 10.0 10.to_s # => "10" '10'.to_i # => 10 'ab'.to_sym # => :ab (1..5).to_a # => [1, 2, 3, 4, 5] to_*
  14. 14. def m(x) if x > 0 puts x elsif x == 0 puts "zero" else puts "-" end end m 1 # + m 0 # zero m -1 # - def u(x) unless x.nil? puts x end end u 1 # 1 u nil ESTRUTURAS DE CONTROLE x = 0 until x == 5 print x x += 1 end # 01234 x = 0 while x < 5 print x x += 1 end # 01234 for x in 0..4 print x end # 01234
  15. 15. a = [] a.push(a.length) while a.length < 3 a.push(a.length) until a.length == 5 p a if a.length > 4 # [0, 1, 2, 3, 4] p a unless a.length < 5 # [0, 1, 2, 3, 4] MODIFICADORES
  16. 16. [1, 2, 3].map { |i| i**2 } # => [1, 4, 9] [1, 2, 3].each do |i| puts i puts i**2 end BLOCOS
  17. 17. def block_yield yield 'BLOCO' end def block_call(&b) b.call 'BLOCO' end block_yield { |s| puts s.downcase } # bloco block_call { |s| puts s.reverse } # OCOLB BLOCOS E ARGUMENTOS
  18. 18. for x in 0..4 print x end (0..4).each { |x| print x} # 01234 FOR MAIS RUBISTA
  19. 19. FILOSOFIA DO RUBY ● Felicidade do programador ● Orientação à objeto ● Flexibilidade
  20. 20. Ruby lhe permite programar de forma eficiente e divertida
  21. 21. TUDO É UM OBJETO “I wanted a scripting language that was more powerful than Perl, and more object-oriented than Python.” Matz -
  22. 22. MODELO DE OBJETOS DO RUBY
  23. 23. class Pessoa attr_accessor :nome, :idade def to_s "Nome: #{nome}; Idade: #{idade}" end end eu = Pessoa.new eu.nome = "Eu" eu.idade=(101) puts eu # Nome: Eu; Idade: 101
  24. 24. class Pessoa attr_accessor :nome, :idade def to_s "Nome: #{nome}; Idade: #{idade}" end end eu = Pessoa.new eu.nome = "Eu" eu.idade=(101) puts eu # Nome: Eu; Idade: 101 self.nome self.idade
  25. 25. module M def self.m(x) puts x end end M.m 1 # 1 MODULE
  26. 26. module M class C def m(x) puts x end end end M::C.new.m 1 # 1 MODULE & NAMESPACE
  27. 27. module M def m(x) puts x end end class C include M end C.new.m 1 # 1 MODULE & MIXIN
  28. 28. CLASSES ABERTAS TODAS as classes podem ser modificadas a qualquer momento (também conhecido como monkeypatch)
  29. 29. 10.length #NoMethodError: undefined method `length' for 10:Fixnum class Fixnum def length to_s.length end end 10.length # => 2 CONTANTO ALGARISMOS DE UM NÚMERO
  30. 30. class C def method_missing(meth, *args) puts "#{meth} chamado com #{args}" yield args if block_given? end end o = C.new z = o.matematica(5, 2) { |x, y| x * y } # matematica chamado com [5, 2] puts z # 10 MÉTODOS FANTASMAS
  31. 31. DSL ● 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)
  32. 32. # rspec describe "true or false" do it "should be true" do expect(true || false).to be true end end # capybara visit '/' fill_in 'e-mail', with: 'mail@mail.com' click_on 'Ok'
  33. 33. ECOSSISTEMA ● RubyGems ● Bundler ● Rake
  34. 34. RAILS ● Framework MVC de aplicações web ● Criado em 2003 por David Heinemeier Hansson ● Extraído do Basecamp ● + de 3400 contribuidores
  35. 35. 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
  36. 36. MVC ORIGINAL
  37. 37. Banco de Dados Rails routes 2 ActionController 1 8 3 ActionView ActiveRecord 4 5 6 7 1. navegador envia a requisição 2. roteamento encontra o controller 3. controller requisita o model 4. model requisita os dados 5. BD retorna os dados 6. model retorna 7. controller requisita a renderização 8. view retorna a página 9. controller responde a requisição HTTP com a página 9 ARQUITETURA
  38. 38. $ rails new notas $ cd notas
  39. 39. 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
  40. 40. DIRETÓRIO test Testes da classes da aplicação ● fixtures: dados para os testes ● integration: testes de integração de todos os componentes da aplicação
  41. 41. 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
  42. 42. 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
  43. 43. gem 'rails-i18n', github: 'svenfuchs/rails-i18n', branch: 'master' gem 'devise' gem 'devise-i18n' gem 'bootstrap-sass' gem 'autoprefixer-rails' gem 'mailcatcher', group: :development Gemfile
  44. 44. $ bundle $ rails g devise:install
  45. 45. config.i18n.default_locale = :"pt-BR" config/application.rb
  46. 46. config.action_mailer.default_url_options = {host: 'localhost'} config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = {address: 'localhost', port: 1025} config/environments/development.rb
  47. 47. $ mailcatcher $ rails s $ rails g devise User $ rake db:migrate
  48. 48. YAML ● Padrão amigável de serialização de dados ● Baseada em indentação
  49. 49. $ rails c > User.new(password: "12345678").encrypted_password => … eu: email: eu@mail.com encrypted_password: "..." voce: email: voce@mail.com encrypted_password: "..." test/fixtures/users.yml
  50. 50. BDD Desenvolvimento Orientado a Comportamento ● Outside-in ● Testa-se o comportamento, não a implementação
  51. 51. gem 'capybara', group: [:development, :test] gem 'capybara-webkit', group: [:development, :test] Gemfile
  52. 52. # … require 'minitest/mock' require 'capybara/rails' # … class ActionDispatch::IntegrationTest include Capybara::DSL end # … class ActionController::TestCase include Devise::TestHelpers end test/test_helper.rb
  53. 53. $ bundle $ rails g integration_test notes_listing
  54. 54. TESTES DE INTEGRAÇÃO (ACEITAÇÃO) ● Testes de alto nível (caixa preta) ● Simulam a interação do usuário ● Baseados em cenários
  55. 55. 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...)
  56. 56. 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 (continua...)
  57. 57. test 'cor das notas' do visit '/' assert page.all('.note .well').first['style'] .include? "background-color: #{notes(:one).color}" end end test/integration/notes_listing_test.rb
  58. 58. $ rake test
  59. 59. Rails.application.routes.draw do resources :notes, path: '/' # ... config/routes.rb
  60. 60. $ rails g controller notes
  61. 61. TESTES FUNCIONAIS ● Testa o resultado de uma funcionalidade ● Efeitos colaterais e resultados intermediários não importam
  62. 62. class NotesControllerTest < ActionController::TestCase test 'index' do get :index assert_response :success assert_not_nil assigns(:notes) end end test/controllers/notes_controller_test.rb
  63. 63. class NotesController < ApplicationController def index @notes = Note.all end end app/controllers/notes_controller.rb
  64. 64. $ rails g model Note title:string body: text color:string user:references $ rake db:migrate
  65. 65. one: title: Nota1 Busca body: Texto1 color: Gold user: eu two: title: Nota2 body: Texto2 color: Gold user: voce test/fixtures/notes.yml (continua)
  66. 66. three: title: Nota3 body: Texto3 Busca color: Gold user: eu four: title: Nota4 body: Texto4 color: Gold user: voce test/fixtures/notes.yml
  67. 67. TESTES UNITÁRIOS ● Testam as menores unidades de funcionalidade ● Não deve haver interação com outros componentes
  68. 68. 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 end end test/models/note_test.rb
  69. 69. class Note < ActiveRecord::Base @allowed_colors = %w(Gold LightGreen Pink SkyBlue) # … validates :body, presence: true validates :color, inclusion: { in: @allowed_colors } end app/models/note.rb
  70. 70. $ rm app/assets/stylesheets/application.css $ touch app/assets/stylesheets/application.css.scss
  71. 71. ASSETS PIPELINE ● Framework para pré-processar, concatenar e minificar JS e CSS ● Reduz a quantidade e o tamanho das requisições
  72. 72. SASS ● Linguagem de script que adiciona funcionalidades ao CSS ● “CSS com superpoderes” ● CSS válido é SCSS válido
  73. 73. body { padding-top: 70px; } @import "bootstrap-sprockets"; @import "bootstrap"; @import "notes" app/assets/stylesheets/application.css.scss
  74. 74. // ... //= require turbolinks //= require bootstrap-sprockets // ... app/assets/javascripts/application.js
  75. 75. TEMPLATES & ERB ● Embedded Ruby (Ruby Incorporado) ● Processa código ruby e gera HTML
  76. 76. <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> app/views/notes/index.html.erb
  77. 77. LAYOUTS ● Template para definir estruturas comuns a outros templates
  78. 78. <!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)
  79. 79. <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 (continua)
  80. 80. <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> app/views/layouts/application.html.erb
  81. 81. INTERNACIONALIZAÇÃO ● Traduções e formatação de números, datas, etc.
  82. 82. pt-BR: actions: log_out: Sair config/locales/pt-BR.yml
  83. 83. $ rake db:fixtures:load $ rails s
  84. 84. 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) end end test/controllers/notes_controller_test.rb
  85. 85. class NotesController < ApplicationController before_action :authenticate_user! def index @notes = Note.where(user: current_user) end end app/controllers/notes_controller.rb
  86. 86. 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 end end test/controllers/notes_controller_test.rb
  87. 87. class NotesController < ApplicationController # ... def index @notes = Note.where(user: current_user) .order(updated_at: :desc) end end app/controllers/notes_controller.rb
  88. 88. $ rails g integration_test note_creation
  89. 89. # ... module JSHelper def use_js Capybara.current_driver = :webkit @js = true end def teardown super if @js Capybara.use_default_driver @js = false end end end test/test_helper.rb (continua)
  90. 90. 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! end end test/test_helper.rb (continua)
  91. 91. # ... class ActionDispatch:: IntegrationTest # ... include JSHelper include SessionHelper end test/test_helper.rb (continua)
  92. 92. # ... class ActiveRecord::Base mattr_accessor :shared_connection @@shared_connection = nil def self.connection @@shared_connection || retrieve_connection end end ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection test/test_helper.rb
  93. 93. class NotesListingTest < ActionDispatch::IntegrationTest setup do log_in end # ... test/integration/notes_listing_test.rb
  94. 94. 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 (continua)
  95. 95. 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' end end test/integration/note_creation_test.rb
  96. 96. <!-- ... --> <div id="navbar" class="navbar-collapse collapse"> <%= link_to t('actions.create_note'), new_note_path, class: "btn btn-success navbar-btn navbar-left" %> <!-- ... --> app/views/layouts/application.html.erb
  97. 97. # ... 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 end end test/controllers/notes_controller_test.rb
  98. 98. class NotesController < ApplicationController # ... def new @note = Note.new(color: 'Gold') end end app/controllers/notes_controller.rb
  99. 99. FORM HELPERS ● Helpers para criação de formulários ● Facilitam a criação de formulários para ações em modelos
  100. 100. <%= 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)
  101. 101. <!-- ... --> <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 (continua)
  102. 102. <!-- ... --> <div class="col-xs-12"> <%= n.submit t('actions.save_note'), class: 'btn btn-success' %> </div> </div> <% end %> app/views/notes/new.html.erb
  103. 103. EIGENCLASS ● Metaclasse ou Classe Singleton ● Implícita e exclusiva de cada objeto
  104. 104. class C; end obj = C.new def obj.hey puts 'hey' end # hey está definido na eigenclass de obj obj.hey # hey C.new.hey # NoMethodError: undefined method `hey' for #<C:0x...>
  105. 105. class Note < ActiveRecord::Base # ... class << self attr_reader :allowed_colors end end app/models/note.rb
  106. 106. COFFEESCRIPT ● Linguagem que compila para JS ● Simplifica o JS ● Inspirado em Ruby, Python e Haskell
  107. 107. @change_color = (color) -> $('#note-card').css 'background-color', color $('input[name="note[color]"]').val color app/assets/javascripts/notes.js.coffee
  108. 108. 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 config/locales/pt-BR.yml
  109. 109. # ... 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 (continua)
  110. 110. 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 end end test/controllers/notes_controllers_test.rb
  111. 111. STRONG PARAMETERS ● Permite controlar especificamente quais atributos podem ser definidos em massa
  112. 112. 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) end end app/controllers/notes_controller.rb
  113. 113. $ rails g integration_test note_editing
  114. 114. 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)
  115. 115. 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' end end test/integration/note_editing_test.rb (continua)
  116. 116. <!-- ... --> <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> app/views/notes/index.html.erb
  117. 117. 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 } end end test/controllers/notes_controller_test.rb
  118. 118. class NotesController < ApplicationController # ... def edit @note = Note.find(params[:id]) end # ... app/controllers/notes_controller.rb
  119. 119. PARTIALS ● DRY ● Extração de partes comuns entre templates
  120. 120. <%= render 'form' %> app/views/notes/{new,edit}.html.erb
  121. 121. pt-BR: actions: create_note: Criar nota edit_note: Editar # ... config/locales/pt-BR.yml
  122. 122. 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 (continua)
  123. 123. 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 } end end test/controllers/notes_controller_test.rb
  124. 124. 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 # ... app/controllers/notes_controller.rb
  125. 125. $ rails g integration_test note_deleting
  126. 126. 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 end end test/integration/note_deleting_test.rb
  127. 127. <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> <!-- ... -->
  128. 128. 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? # ... config/locales/pt-BR.yml
  129. 129. 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 test/controllers/notes_controller_test.rb (continua)
  130. 130. MOCKS & STUBS ● Mocks: simulam o comportamento de objetos reais ● Stubs: simulam a execução de métodos reais
  131. 131. 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 end end test/controllers/notes_controller_test.rb
  132. 132. 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 # ... app/controllers/notes_controller.rb
  133. 133. $ rails g integration_test notes_searching
  134. 134. 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)
  135. 135. 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)
  136. 136. 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 test/integration/notes_searching_test.rb (continua)
  137. 137. <!-- ... --> <%= 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 %> <!-- ... --> app/views/layouts/application.erb.html
  138. 138. 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 end end test/integration/notes_searching_test.rb
  139. 139. Rails.application.routes.draw do resources :notes, path: '/' do get 'search' => 'notes#search', on: :collection, as: :search end # ... config/routes.rb
  140. 140. 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)
  141. 141. 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 (continua)
  142. 142. 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 test/controllers/notes_controllers_test.rb
  143. 143. 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/controllers/notes_controller.rb
  144. 144. <%= render 'list' %> app/views/notes/index.html.erb
  145. 145. <%= render 'list' %> app/views/notes/search.html.erb
  146. 146. 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 # ... config/locales/pt-BR.yml
  147. 147. REFERÊNCIAS PERROTTA, 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.
  148. 148. REFERÊNCIAS About Ruby - https://www.ruby-lang.org/en/about/ The Philosophy of Ruby - http://www.artima. com/intv/rubyP.html RubyConf: History of Ruby - http://blog.nicksieger. com/articles/2006/10/20/rubyconf-history-of-ruby/ Ruby on Rails Guides - http://guides.rubyonrails.org/

×