RUBY ON RAILS 
MAURÍ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
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
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
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
s = 'abc' 
s.reverse # => 'cba' 
s # => 'abc' 
s.reverse! # => 'cba' 
s # => 'cba' 
s.include? 'a' # => true 
MÉTODOS
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
'abc'.reverse # => "cba" 
'abc'.send :reverse # => "cba" 
MÉTODOS MENSAGENS
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_*
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
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
[1, 2, 3].map { |i| i**2 } # => [1, 4, 9] 
[1, 2, 3].each do |i| 
puts i 
puts i**2 
end 
BLOCOS
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
for x in 0..4 
print x 
end 
(0..4).each { |x| print x} # 01234 
FOR MAIS RUBISTA
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}" 
end 
end 
eu = Pessoa.new 
eu.nome = "Eu" 
eu.idade=(101) 
puts eu # Nome: Eu; Idade: 101
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
module M 
def self.m(x) 
puts x 
end 
end 
M.m 1 # 1 
MODULE
module M 
class C 
def m(x) 
puts x 
end 
end 
end 
M::C.new.m 1 # 1 
MODULE & NAMESPACE
module M 
def m(x) 
puts x 
end 
end 
class C 
include M 
end 
C.new.m 1 # 1 
MODULE & MIXIN
CLASSES ABERTAS 
TODAS as classes podem ser modificadas a 
qualquer momento (também conhecido como 
monkeypatch)
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
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
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)
# 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'
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 
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
$ 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 test 
Testes 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
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
$ bundle 
$ rails g devise:install
config.i18n.default_locale = :"pt-BR" 
config/application.rb
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
$ mailcatcher 
$ rails s 
$ rails g devise User 
$ rake db:migrate
YAML 
● Padrão amigável de serialização de dados 
● Baseada em indentação
$ 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
BDD 
Desenvolvimento Orientado a 
Comportamento 
● Outside-in 
● Testa-se o comportamento, não a 
implementação
gem 'capybara', group: [:development, :test] 
gem 'capybara-webkit', group: [:development, :test] 
Gemfile
# … 
require 'minitest/mock' 
require 'capybara/rails' 
# … 
class ActionDispatch::IntegrationTest 
include Capybara::DSL 
end 
# … 
class ActionController::TestCase 
include Devise::TestHelpers 
end 
test/test_helper.rb
$ 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
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 (continua...)
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
$ rake test
Rails.application.routes.draw do 
resources :notes, path: '/' 
# ... 
config/routes.rb
$ rails g controller notes
TESTES FUNCIONAIS 
● Testa o resultado de uma funcionalidade 
● Efeitos colaterais e resultados intermediários 
não importam
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
class NotesController < ApplicationController 
def index 
@notes = Note.all 
end 
end 
app/controllers/notes_controller.rb
$ rails g model Note title:string  body: 
text color:string user:references 
$ rake db:migrate
one: 
title: Nota1 Busca 
body: Texto1 
color: Gold 
user: eu 
two: 
title: Nota2 
body: Texto2 
color: Gold 
user: voce 
test/fixtures/notes.yml (continua)
three: 
title: Nota3 
body: Texto3 Busca 
color: Gold 
user: eu 
four: 
title: Nota4 
body: Texto4 
color: Gold 
user: voce 
test/fixtures/notes.yml
TESTES UNITÁRIOS 
● Testam as menores unidades de 
funcionalidade 
● Não deve haver interação com outros 
componentes
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
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
$ 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
body { padding-top: 70px; } 
@import "bootstrap-sprockets"; 
@import "bootstrap"; 
@import "notes" 
app/assets/stylesheets/application.css.scss
// ... 
//= require turbolinks 
//= require bootstrap-sprockets 
// ... 
app/assets/javascripts/application.js
TEMPLATES & ERB 
● Embedded Ruby (Ruby Incorporado) 
● Processa código ruby e gera HTML
<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
LAYOUTS 
● Template para definir estruturas comuns a 
outros templates
<!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 (continua)
<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
INTERNACIONALIZAÇÃO 
● Traduções e formatação de números, datas, 
etc.
pt-BR: 
actions: 
log_out: Sair 
config/locales/pt-BR.yml
$ rake db:fixtures:load 
$ rails s
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
class NotesController < 
ApplicationController 
before_action :authenticate_user! 
def index 
@notes = Note.where(user: current_user) 
end 
end 
app/controllers/notes_controller.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 
end 
end 
test/controllers/notes_controller_test.rb
class NotesController < ApplicationController 
# ... 
def index 
@notes = Note.where(user: current_user) 
.order(updated_at: :desc) 
end 
end 
app/controllers/notes_controller.rb
$ rails g integration_test note_creation
# ... 
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)
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)
# ... 
class ActionDispatch:: 
IntegrationTest 
# ... 
include JSHelper 
include SessionHelper 
end 
test/test_helper.rb (continua)
# ... 
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
class NotesListingTest < ActionDispatch::IntegrationTest 
setup do 
log_in 
end 
# ... 
test/integration/notes_listing_test.rb
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)
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
<!-- ... --> 
<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
# ... 
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
class NotesController < 
ApplicationController 
# ... 
def new 
@note = Note.new(color: 'Gold') 
end 
end 
app/controllers/notes_controller.rb
FORM HELPERS 
● Helpers para criação de formulários 
● Facilitam a criação de formulários para 
ações em modelos
<%= 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 (continua)
<!-- ... --> 
<div class="col-xs-12"> 
<%= n.submit t('actions.save_note'), 
class: 'btn btn-success' %> 
</div> 
</div> 
<% end %> 
app/views/notes/new.html.erb
EIGENCLASS 
● Metaclasse ou Classe Singleton 
● Implícita e exclusiva de cada objeto
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...>
class Note < ActiveRecord::Base 
# ... 
class << self 
attr_reader :allowed_colors 
end 
end 
app/models/note.rb
COFFEESCRIPT 
● Linguagem que compila para JS 
● Simplifica o JS 
● Inspirado em Ruby, Python e Haskell
@change_color = (color) -> 
$('#note-card').css 'background-color', color 
$('input[name="note[color]"]').val color 
app/assets/javascripts/notes.js.coffee
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
# ... 
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)
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
STRONG PARAMETERS 
● Permite controlar especificamente quais 
atributos podem ser definidos em massa
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
$ rails g integration_test note_editing
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' 
end 
end 
test/integration/note_editing_test.rb (continua)
<!-- ... --> 
<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
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
class NotesController < ApplicationController 
# ... 
def edit 
@note = Note.find(params[:id]) 
end 
# ... 
app/controllers/notes_controller.rb
PARTIALS 
● DRY 
● Extração de partes comuns entre templates
<%= render 'form' %> 
app/views/notes/{new,edit}.html.erb
pt-BR: 
actions: 
create_note: Criar nota 
edit_note: Editar 
# ... 
config/locales/pt-BR.yml
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)
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
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
$ rails g integration_test note_deleting
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
<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> 
<!-- ... -->
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
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)
MOCKS & STUBS 
● Mocks: simulam o comportamento de 
objetos reais 
● Stubs: simulam a execução de métodos 
reais
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
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
$ rails g integration_test notes_searching
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 
test/integration/notes_searching_test.rb (continua)
<!-- ... --> 
<%= 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
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
Rails.application.routes.draw do 
resources :notes, path: '/' do 
get 'search' => 'notes#search', on: :collection, as: :search 
end 
# ... 
config/routes.rb
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 (continua)
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
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
<%= render 'list' %> 
app/views/notes/index.html.erb
<%= render 'list' %> 
app/views/notes/search.html.erb
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
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.
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/

Minicurso Ruby on Rails

  • 1.
    RUBY ON RAILS MAURÍCIO EDUARDO LOSCHI BATISTA
  • 2.
    RUBY ● Interpretada ● Dinâmica ● Multiparadigma
  • 3.
    HISTÓRIA ● Criadapor Yukihiro “Matz” Matsumoto em Fev/1993. ● Primeira release pública (v0.95) em Dez/1995 ● Primeira versão estável em Dez/1996
  • 4.
    AS INSPIRAÇÕES DE MATZ ● Perl ● Smalltalk ● Eiffel ● Ada ● Lisp
  • 5.
    RUBY POR MATZ “Ruby is simple in appearance, but is very complex inside, just like our human body.” Matz -
  • 6.
  • 7.
    1 # númerointeiro '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.
    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.
    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.
    s = 'abc' s.reverse # => 'cba' s # => 'abc' s.reverse! # => 'cba' s # => 'cba' s.include? 'a' # => true MÉTODOS
  • 11.
    def splat(*args) pargs 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.
    'abc'.reverse # =>"cba" 'abc'.send :reverse # => "cba" MÉTODOS MENSAGENS
  • 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.
    def m(x) ifx > 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.
    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.
    [1, 2, 3].map{ |i| i**2 } # => [1, 4, 9] [1, 2, 3].each do |i| puts i puts i**2 end BLOCOS
  • 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.
    for x in0..4 print x end (0..4).each { |x| print x} # 01234 FOR MAIS RUBISTA
  • 19.
    FILOSOFIA DO RUBY ● Felicidade do programador ● Orientação à objeto ● Flexibilidade
  • 20.
    Ruby lhe permiteprogramar de forma eficiente e divertida
  • 21.
    TUDO É UMOBJETO “I wanted a scripting language that was more powerful than Perl, and more object-oriented than Python.” Matz -
  • 22.
  • 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.
    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.
    module M defself.m(x) puts x end end M.m 1 # 1 MODULE
  • 26.
    module M classC def m(x) puts x end end end M::C.new.m 1 # 1 MODULE & NAMESPACE
  • 27.
    module M defm(x) puts x end end class C include M end C.new.m 1 # 1 MODULE & MIXIN
  • 28.
    CLASSES ABERTAS TODASas classes podem ser modificadas a qualquer momento (também conhecido como monkeypatch)
  • 29.
    10.length #NoMethodError: undefinedmethod `length' for 10:Fixnum class Fixnum def length to_s.length end end 10.length # => 2 CONTANTO ALGARISMOS DE UM NÚMERO
  • 30.
    class C defmethod_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.
    DSL ● Linguagemdedicada 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.
    # 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.
    ECOSSISTEMA ● RubyGems ● Bundler ● Rake
  • 34.
    RAILS ● FrameworkMVC de aplicações web ● Criado em 2003 por David Heinemeier Hansson ● Extraído do Basecamp ● + de 3400 contribuidores
  • 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.
  • 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.
    $ rails newnotas $ cd notas
  • 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.
    DIRETÓRIO test Testesda classes da aplicação ● fixtures: dados para os testes ● integration: testes de integração de todos os componentes da aplicação
  • 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.
    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.
    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.
    $ bundle $rails g devise:install
  • 45.
  • 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.
    $ mailcatcher $rails s $ rails g devise User $ rake db:migrate
  • 48.
    YAML ● Padrãoamigável de serialização de dados ● Baseada em indentação
  • 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.
    BDD Desenvolvimento Orientadoa Comportamento ● Outside-in ● Testa-se o comportamento, não a implementação
  • 51.
    gem 'capybara', group:[:development, :test] gem 'capybara-webkit', group: [:development, :test] Gemfile
  • 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.
    $ bundle $rails g integration_test notes_listing
  • 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.
    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.
    test 'listagem denotas' 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.
    test 'cor dasnotas' do visit '/' assert page.all('.note .well').first['style'] .include? "background-color: #{notes(:one).color}" end end test/integration/notes_listing_test.rb
  • 58.
  • 59.
    Rails.application.routes.draw do resources:notes, path: '/' # ... config/routes.rb
  • 60.
    $ rails gcontroller notes
  • 61.
    TESTES FUNCIONAIS ●Testa o resultado de uma funcionalidade ● Efeitos colaterais e resultados intermediários não importam
  • 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.
    class NotesController <ApplicationController def index @notes = Note.all end end app/controllers/notes_controller.rb
  • 64.
    $ rails gmodel Note title:string body: text color:string user:references $ rake db:migrate
  • 65.
    one: title: Nota1Busca body: Texto1 color: Gold user: eu two: title: Nota2 body: Texto2 color: Gold user: voce test/fixtures/notes.yml (continua)
  • 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.
    TESTES UNITÁRIOS ●Testam as menores unidades de funcionalidade ● Não deve haver interação com outros componentes
  • 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.
    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.
    $ rm app/assets/stylesheets/application.css $ touch app/assets/stylesheets/application.css.scss
  • 71.
    ASSETS PIPELINE ●Framework para pré-processar, concatenar e minificar JS e CSS ● Reduz a quantidade e o tamanho das requisições
  • 72.
    SASS ● Linguagemde script que adiciona funcionalidades ao CSS ● “CSS com superpoderes” ● CSS válido é SCSS válido
  • 73.
    body { padding-top:70px; } @import "bootstrap-sprockets"; @import "bootstrap"; @import "notes" app/assets/stylesheets/application.css.scss
  • 74.
    // ... //=require turbolinks //= require bootstrap-sprockets // ... app/assets/javascripts/application.js
  • 75.
    TEMPLATES & ERB ● Embedded Ruby (Ruby Incorporado) ● Processa código ruby e gera HTML
  • 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.
    LAYOUTS ● Templatepara definir estruturas comuns a outros templates
  • 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.
    <ul class="nav navbar-navnavbar-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.
    <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.
    INTERNACIONALIZAÇÃO ● Traduçõese formatação de números, datas, etc.
  • 82.
    pt-BR: actions: log_out:Sair config/locales/pt-BR.yml
  • 83.
  • 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.
    class NotesController < ApplicationController before_action :authenticate_user! def index @notes = Note.where(user: current_user) end end app/controllers/notes_controller.rb
  • 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.
    class NotesController <ApplicationController # ... def index @notes = Note.where(user: current_user) .order(updated_at: :desc) end end app/controllers/notes_controller.rb
  • 88.
    $ rails gintegration_test note_creation
  • 89.
    # ... moduleJSHelper 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.
    module SessionHelper deflog_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.
    # ... classActionDispatch:: IntegrationTest # ... include JSHelper include SessionHelper end test/test_helper.rb (continua)
  • 92.
    # ... classActiveRecord::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.
    class NotesListingTest <ActionDispatch::IntegrationTest setup do log_in end # ... test/integration/notes_listing_test.rb
  • 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.
    test 'erro nacriaçã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.
    <!-- ... --> <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.
    # ... 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.
    class NotesController < ApplicationController # ... def new @note = Note.new(color: 'Gold') end end app/controllers/notes_controller.rb
  • 99.
    FORM HELPERS ●Helpers para criação de formulários ● Facilitam a criação de formulários para ações em modelos
  • 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.
    <!-- ... --> <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.
    <!-- ... --> <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.
    EIGENCLASS ● Metaclasseou Classe Singleton ● Implícita e exclusiva de cada objeto
  • 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.
    class Note <ActiveRecord::Base # ... class << self attr_reader :allowed_colors end end app/models/note.rb
  • 106.
    COFFEESCRIPT ● Linguagemque compila para JS ● Simplifica o JS ● Inspirado em Ruby, Python e Haskell
  • 107.
    @change_color = (color)-> $('#note-card').css 'background-color', color $('input[name="note[color]"]').val color app/assets/javascripts/notes.js.coffee
  • 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.
    # ... 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.
    test 'create deverenderizar 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.
    STRONG PARAMETERS ●Permite controlar especificamente quais atributos podem ser definidos em massa
  • 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.
    $ rails gintegration_test note_editing
  • 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.
    test 'erro naediçã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.
    <!-- ... --> <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.
    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.
    class NotesController <ApplicationController # ... def edit @note = Note.find(params[:id]) end # ... app/controllers/notes_controller.rb
  • 119.
    PARTIALS ● DRY ● Extração de partes comuns entre templates
  • 120.
    <%= render 'form'%> app/views/notes/{new,edit}.html.erb
  • 121.
    pt-BR: actions: create_note:Criar nota edit_note: Editar # ... config/locales/pt-BR.yml
  • 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.
    test 'update deverenderizar 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.
    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.
    $ rails gintegration_test note_deleting
  • 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.
    <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.
    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.
    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.
    MOCKS & STUBS ● Mocks: simulam o comportamento de objetos reais ● Stubs: simulam a execução de métodos reais
  • 131.
    test 'destroy devecolocar 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.
    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.
    $ rails gintegration_test notes_searching
  • 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.
    test 'buscando portexto 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.
    test 'buscando portexto 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.
    <!-- ... --> <%= 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.
    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.
    Rails.application.routes.draw do resources:notes, path: '/' do get 'search' => 'notes#search', on: :collection, as: :search end # ... config/routes.rb
  • 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.
    test 'search devebuscar 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.
    test 'search deveretornar 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.
    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.
    <%= render 'list'%> app/views/notes/index.html.erb
  • 145.
    <%= render 'list'%> app/views/notes/search.html.erb
  • 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.
    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.
    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/