2. Definindo Models
Vamos definir a entidade User para
representar os usuários de um sistema
Um usuário possui nome, email e idade.
@jlucasps
User
Active
Record
users
.......
aplicação rails banco de dados
4. Migrations
Migrations disponibilizam uma forma
conveniênte para alterar o seu banco de dados
de uma maneira estruturada e organizada
ActiveRecord armazena quais migrations
foram executadas utilizando timestamps
Tudo o que precisamos fazer é atualizar o
código e executar $ rake db:migrate
Vamos analizar a anatomia de uma migration
@jlucasps
5. Migrations
@jlucasps
class CreateProducts < ActiveRecord::Migration
def up
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
def down
drop_table :products
end
end
release (up) e rollback (down)
6. Migrations
@jlucasps
release-rollback (change)
class AddSizeToProducts < ActiveRecord::
Migration
def change
add_column :products, :size, :integer, :null =>
:false
end
end
Rails consegue identificar rollback a partir da
release, bem como release a partir do rollback
release <-> rollback
7. Migrations
Migrations são subclasses de ActiveRecord::
Migration
Implementam dois métodos: up e down
(change)
E uma série de métodos auxiliares
@jlucasps
8. Migrations
ActiveRecord fornece métodos que executam
tarefas comuns de definição e manipulação de
dado.
Realizam esta tarefa de forma independente de
um banco de dados específico
@jlucasps
Data Definition Language (DDL)
CREATE
ALTER
DROP
Data Manipulation Language (DML)
SELECT
UPDATE
DELETE
10. Migrations
ActiveRecord suporta os seguintes tipos de
dados para colunas do banco:
@jlucasps
:binary
:boolean
:date
:datetime
:decimal
:float
:integer
:primary_key
:string
:text
:time
:timestamp
11. Migrations
Vamos gerar a Migration, implementá-la e
executá-la para criar a tabela users
@jlucasps
class CreateTableUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.column :name, :string
t.column :email, :string
t.column :age, :integer
t.timestamps
end
end
end
jlucasps@lotus:/media/first_app$ rails g migration
CreateUsersTable
invoke active_record
create db/migrate/20130613063127_create_users_table.rb
13. Criando Models
Vamos criar o model User para representar
entidades mapeadas na tabela users
@jlucasps
class User < ActiveRecord::Base
# Attrs accessible
attr_accessible :name, :email, :age
# Validations
# Associations
# Scopes
# Públic methods
end
14. Testes unitários nos models
Adicionar a gem shoulda e executar $ bundle install
Criar o arquivo /spec/models/user_spec.rb
@jlucasps
# -*- coding: utf-8 -*-
require 'spec_helper'
describe User do
it { should allow_mass_assignment_of :name }
it { should allow_mass_assignment_of :email }
it "creates an user" do
user = User.new(:name => "João Lucas", :email =>
"jlucasps@gmail.com", :age => 24)
user.save.should be_true
end
end
15. Testes unitários nos models
Executar $ rspec
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec
....FFF
Failures:
1) User
2) User
3) User creates an user
Finished in 0.35138 seconds
7 examples, 3 failures
Failed examples:
rspec ./spec/models/user_spec.rb:7 # User
rspec ./spec/models/user_spec.rb:6 # User
rspec ./spec/models/user_spec.rb:9 # User creates an user
Randomized with seed 10444
16. Testes unitários nos models
Executar $ rake db:test:load e $ rspec
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$
rake db:test:load
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$
rspec
.......
Finished in 0.41314 seconds
7 examples, 0 failures
$ git status
$ git add .
$ git commit -m "Testes unitários no modelo User"
17. Mudanças no negócio
E se quisermos que os campos nome e email
sejam obrigatórios?
@jlucasps
18. Mudanças no negócio
Vamos implementar testes para nossa nova regra
@jlucasps
# -*- coding: utf-8 -*-
require 'spec_helper'
describe User do
it { should allow_mass_assignment_of :name }
it { should allow_mass_assignment_of :email }
it "creates an user" do
user = User.new(:name => "João Lucas", :email => "jlucasps@gmail.com", :
age => 24)
user.save.should be_true
end
it "fail to create a user when name is blank" do
user = User.new(:email => "jlucasps@gmail.com", :age => 24)
user.save.should be_false
end
it "fail to create a user when email is blank" do
user = User.new(:name => "João Lucas", :age => 24)
user.save.should be_false
end
end
19. Mudanças no negócio
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec
.......FF
Failures:
1) User fail to create a user when name is blank
Failure/Error: user.save.should be_false
expected: false value
got: true
# ./spec/models/user_spec.rb:16:in `block (2 levels) in <top (required)>'
2) User fail to create a user when email is blank
Failure/Error: user.save.should be_false
expected: false value
got: true
# ./spec/models/user_spec.rb:21:in `block (2 levels) in <top (required)>'
Finished in 0.37949 seconds
9 examples, 2 failures
Failed examples:
rspec ./spec/models/user_spec.rb:14 # User fail to create a user when name
is blank
rspec ./spec/models/user_spec.rb:19 # User fail to create a user when email
is blank
Randomized with seed 46469
20. Mudanças no negócio
O que devemos implementar para o teste
passar?
Implementar alterações no banco (not null)
Implementar alterações no model (validations)
@jlucasps
jlucasps@lotus:/first_app$ rails g migration
NotNullToNameAndEmailToUsers
invoke active_record
create
db/migrate/20130613074955_not_null_to_name_and_email_to_users.rb
21. Mudanças no negócio
@jlucasps
class NotNullToNameAndEmailToUsers < ActiveRecord::Migration
def up
change_column :users, :name, :string, :null => false
change_column :users, :email, :string, :null => false
end
def down
change_column :users, :name, :string, :null => true
change_column :users, :email, :string, :null => true
end
end
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:migrate
== NotNullToNameAndEmailToUsers: migrating
===================================
-- change_column(:users, :name, :string, {:null=>false})
-> 0.0106s
-- change_column(:users, :email, :string, {:null=>false})
-> 0.0038s
== NotNullToNameAndEmailToUsers: migrated (0.0146s)
==========================
23. Mudanças no negócio
@jlucasps
jlucasps@lotus:/media/truecrypt1/first_app$ rake db:
test:load
jlucasps@lotus:/media/truecrypt1/first_app$ rspec
.........
Finished in 0.50004 seconds
9 examples, 0 failures
Randomized with seed 2195
$ git status
$ git add .
$ git commit -m "Não permitir que sejam criados usuários
com nome ou email em branco."
24. Mudanças no negócio
Outra vez as regras mudaram, agora
precisamos adicionar um campo para opção
sexual do usuário.
*Não há necessidade do campo ser obrigatório
Quais os passos?
@jlucasps
Testes Código Refactoring
25. Mudanças no negócio
@jlucasps
# -*- coding: utf-8 -*-
require 'spec_helper'
describe User do
...................
...................
it "creates a user with gender value MALE" do
user = User.new(:name => "Bob Dylan", :email => "bob@dylan.
com", :age => 72, :gender => User::MALE)
user.save.should be_true
end
it "creates a user with gender value FEMALE" do
user = User.new(:name => "Candice Swanepoel", :email =>
"candice@swanepoel.com", :age => 24, :gender => User::FEMALE)
user.save.should be_true
end
end
26. Mudanças no negócio
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec
.......FF..
Failures:
1) User fail to create a user with gender value MALE
Failure/Error: user = User.new(:name => "Bob Dylan", :email =>
"bob@dylan.com", :age => 72, :gender => User::MALE)
NameError:
uninitialized constant User::MALE
# ./spec/models/user_spec.rb:25:in `block (2 levels) in <top (required)>'
2) User fail to create a user with gender value FEMALE
Failure/Error: user = User.new(:name => "Candice Swanepoel", :email =>
"candice@swanepoel.com", :age => 24, :gender => User::FEMALE)
NameError:
uninitialized constant User::FEMALE
# ./spec/models/user_spec.rb:30:in `block (2 levels) in <top (required)>'
Finished in 0.38009 seconds
11 examples, 2 failures
Failed examples:
rspec ./spec/models/user_spec.rb:24 # User fail to create a user with gender
value MALE
rspec ./spec/models/user_spec.rb:29 # User fail to create a user with gender
value FEMALE
Randomized with seed 23755
27. Mudanças no negócio
@jlucasps
class User < ActiveRecord::Base
# Attrs accessible
attr_accessible :name, :email, :age, :gender
# Constants
MALE = 1
FEMALE = 2
OTHER = 3
# Validations
validates :name, :presence => true, :allow_blank => false
validates :email, :presence => true, :allow_blank => false
# Associations
# Scopes
# Públic methods
end
28. Mudanças no negócio
@jlucasps
jlucasps@lotus:/media/first_app$ rails g migration
AddGenderToUsers gender:integer
invoke active_record
create db/migrate/20130613081853_add_gender_to_users.rb
class AddGenderToUsers < ActiveRecord::Migration
def change
add_column :users, :gender, :integer
end
end
29. Mudanças no negócio
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:migrate
== AddGenderToUsers: migrating
===============================================
-- add_column(:users, :gender, :integer)
-> 0.0009s
== AddGenderToUsers: migrated (0.0010s)
======================================
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:test:load
...........
Finished in 0.38494 seconds
11 examples, 0 failures
Randomized with seed 52935
$ git status
$ git add .
$ git commit -m "Adicionando campo gender no modelo User."
30. Mudanças no negócio
E se for necessário informar o sexo caso a idade seja maior
ou igual a 18?
@jlucasps
context "when age >= 18" do
it "creates an user with gender value" do
user = User.new(:name => "João Lucas", :email => "jlucasps@gmail.com", :age => 18, :
gender => User::MALE)
user.save.should be_true
end
it "does not create an user without gender value" do
user = User.new(:name => "João Lucas", :email => "jlucasps@gmail.com", :age => 18)
user.save.should be_false
end
end
context "when age < 18" do
it "creates an user with gender value" do
user = User.new(:name => "João Lucas", :email => "jlucasps@gmail.com", :age => 17, :
gender => User::MALE)
user.save.should be_true
end
it "does not create an user without gender value" do
user = User.new(:name => "João Lucas", :email => "jlucasps@gmail.com", :age => 17)
user.save.should be_true
end
end
31. Mudanças no negócio
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec
...........F....
Failures:
1) User when age >= 18 does not create an user without gender
value
Failure/Error: user.save.should be_false
expected: false value
got: true
# ./spec/models/user_spec.rb:48:in `block (3 levels) in <top
(required)>'
Finished in 0.40493 seconds
16 examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:46 # User when age >= 18 does
not create an user without gender value
32. Mudanças no negócio
@jlucasps
class User < ActiveRecord::Base
# Attrs accessible
attr_accessible :name, :email, :age, :gender
# Constants
MALE = 1
FEMALE = 2
OTHER = 3
# Validations
validates :name, :presence => true, :allow_blank => false
validates :email, :presence => true, :allow_blank => false
validates :gender, :presence => true, :if => :adulthood
# Associations
# Scopes
# Públic methods
def adulthood
age >= 18
end
end
34. Mudanças no negócio
@jlucasps
Vasmo modificar nosso código para que não
seja possível o cadastro de dois usuários com o
mesmo email.
Vale lembrar que, além da validação no
modelo, podemos também implementar uma
validação no banco de dados para garantir
unicidade de uma coluna.
No entanto, primeiro, vamos aos testes.
35. Mudanças no negócio
@jlucasps
Vamos adicionar o seguinte contexto à nossa
suite de testes e executar $ rspec
context "when tries to create two users with same email" do
it "create two users with differente emails" do
user_1 = User.create(:name => "Primeiro usuário", :email =>
"email_1@gmail.com")
user_2 = User.new(:name => "Segundo usuário", :email =>
"email_2@gmail.com")
user_2.save.should be_true
end
it "does no create two users with same emails" do
user_1 = User.create(:name => "Primeiro usuário", :email =>
"email_1@gmail.com")
user_2 = User.new(:name => "Segundo usuário", :email =>
"email_1@gmail.com")
user_2.save.should be_false
end
end
36. Mudanças no negócio
@jlucasps
Verificamos que foram reportados dois erros
que não esperávamos
1) User when tries to create two users with same email create two users with
differente emails
Failure/Error: user_1 = User.create(:name => "Primeiro usuário", :email
=> "email_1@gmail.com")
NoMethodError:
undefined method `>=' for nil:NilClass
# ./app/models/user.rb:21:in `adulthood'
# ./spec/models/user_spec.rb:68:in `block (3 levels) in <top (required)>'
2) User when tries to create two users with same email does no create two
users with same emails
Failure/Error: user_1 = User.create(:name => "Primeiro usuário", :email
=> "email_1@gmail.com")
NoMethodError:
undefined method `>=' for nil:NilClass
# ./app/models/user.rb:21:in `adulthood'
# ./spec/models/user_spec.rb:74:in `block (3 levels) in <top (required)>'
37. Mudanças no negócio
@jlucasps
Qual o motivo dos erros inesperados?
Vamos implementar alguns testes para o método
adulthood, verificar os erros e atualizar o código
describe "#adulthood" do
it "is adult when age == 18" do
user = User.new(:name => "Nome", :email => "email@gmail.com", :age => 18)
user.adulthood.should be_true
end
it "is adult when age > 18" do
user = User.new(:name => "Nome", :email => "email@gmail.com", :age => 30)
user.adulthood.should be_true
end
it "is not adult when age < 18" do
user = User.new(:name => "Nome", :email => "email@gmail.com", :age => 17)
user.adulthood.should be_false
end
it "is not adult when age is blank" do
user = User.new(:name => "Nome", :email => "email@gmail.com")
user.adulthood.should be_false
end
end
38. Mudanças no negócio
@jlucasps
Atualizando o model User
class User < ActiveRecord::Base
# Attrs accessible
attr_accessible :name, :email, :age, :gender
# Constants
MALE = 1
FEMALE = 2
OTHER = 3
# Validations
validates :name, :presence => true, :allow_blank => false
validates :email, :presence => true, :allow_blank => false
validates :gender, :presence => true, :if => :adulthood
# Associations
# Scopes
# Públic methods
def adulthood
self.age.present? and age >= 18
end
end
39. Mudanças no negócio
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec
............F.........
Failures:
1) User when tries to create two users with same email does no create two users
with same emails
Failure/Error: user_2.save.should be_false
expected: false value
got: true
# ./spec/models/user_spec.rb:76:in `block (3 levels) in <top (required)>'
Finished in 0.40533 seconds
22 examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:73 # User when tries to create two users with
same email does no create two users with same emails
Randomized with seed 45305
Agora sim, encontramos o erro esperado na criação de
dois usuários com mesmo email.
40. Mudanças no negócio
@jlucasps
first_app$ rails g migration AddUniqueToEmail
class AddUniqueToEmail < ActiveRecord::
Migration
def change
add_index :users, :email, :unique => true
end
end
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:migrate
== AddUniqueToEmail: migrating
===============================================
-- add_index(:users, :email, {:unique=>true})
-> 0.0009s
== AddUniqueToEmail: migrated (0.0010s)
========================================
41. Mudanças no negócio
@jlucasps
class User < ActiveRecord::Base
# Attrs accessible
attr_accessible :name, :email, :age, :gender
# Constants
MALE = 1
FEMALE = 2
OTHER = 3
# Validations
validates :name, :presence => true, :allow_blank => false
validates :email, :presence => true, :allow_blank => false
validates_uniqueness_of :email
validates :gender, :presence => true, :if => :adulthood
# Associations
# Scopes
# Públic methods
def adulthood
self.age.present? and age >= 18
end
end