Post on 15-Jul-2015
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
RUBY POR MATZ
“Ruby is simple in appearance, but is very complex inside, just like our human body.”
Matz -
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
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 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
TUDO É UM OBJETO
“I wanted a scripting language that was more powerful than Perl, and more object-oriented
than Python.”Matz -
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
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: 'mail@mail.com'click_on 'Ok'
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
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
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
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}
test/fixtures/users.yml
eu: email: eu@mail.com encrypted_password: "..."
voce: email: voce@mail.com 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
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
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
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
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// ...
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>
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>
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
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 %>
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
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
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 # ...
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 # ...
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 # ...
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 # ...
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/