Desenvolvimento web com Ruby on Rails (parte 3)

Preview:

DESCRIPTION

 

Citation preview

Desenvolvimento Web com Ruby on

Rails

João Lucas Pereira de Santanagtalk | linkedin | twitter: jlucasps

Definindo Models

Vamos definir a entidade User para representar os usuários de um sistemaUm usuário possui nome, email e idade.

@jlucasps

UserActiveRecord

users

.......

aplicação rails banco de dados

Definindo Models

@jlucasps

users

name email age

usersid name email age created_at updated_at

Como criar a tabela no banco de dados utilizando o Rails ?

Migrations

Migrations disponibilizam uma forma conveniênte para alterar o seu banco de dados de uma maneira estruturada e organizadaActiveRecord armazena quais migrations foram executadas utilizando timestampsTudo o que precisamos fazer é atualizar o código e executar $ rake db:migrate

Vamos analizar a anatomia de uma migration

@jlucasps

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 endend

release (up) e rollback (down)

Migrations

@jlucasps

release-rollback (change)

class AddSizeToProducts < ActiveRecord::Migration def change add_column :products, :size, :integer, :null => :false endend

Rails consegue identificar rollback a partir da release, bem como release a partir do rollback

release <-> rollback

Migrations

Migrations são subclasses de ActiveRecord::MigrationImplementam dois métodos: up e down (change)E uma série de métodos auxiliares

@jlucasps

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

MigrationsMétodos para definição de dados

@jlucasps

create_table(:name, options)drop_table(:name)rename_table(:old_name, :new_name)add_column(:table_name, :column_name, :type, options)rename_column(:table_name, :column_name, :new_column_name)change_column(:table_name, :column_name, :type, options)remove_column(:table_name, :column_names)add_index(:table_name, :column_names, options)remove_index(:table_name, :column => :column_name)remove_index(:table_name, :name => :index_name)

MigrationsActiveRecord suporta os seguintes tipos de dados para colunas do banco:

@jlucasps

:binary:boolean:date:datetime:decimal:float:integer:primary_key:string:text:time:timestamp

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 endend

jlucasps@lotus:/media/first_app$ rails g migration CreateUsersTable invoke active_record create db/migrate/20130613063127_create_users_table.rb

Migrations

@jlucasps

jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:migrate== CreateTableUsers: migrating ===============================================-- create_table(:users) -> 0.0491s== CreateTableUsers: migrated (0.0492s) ======================================

jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:rollback== CreateTableUsers: reverting ===============================================-- drop_table("users") -> 0.0008s== CreateTableUsers: reverted (0.0009s) ======================================

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

Testes unitários nos models

Adicionar a gem shoulda e executar $ bundle installCriar 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 endend

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 seconds7 examples, 3 failures Failed examples: rspec ./spec/models/user_spec.rb:7 # Userrspec ./spec/models/user_spec.rb:6 # Userrspec ./spec/models/user_spec.rb:9 # User creates an user Randomized with seed 10444

Testes unitários nos models

Executar $ rake db:test:load e $ rspec

@jlucasps

jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:test:loadjlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec.......Finished in 0.41314 seconds7 examples, 0 failures

$ git status$ git add .$ git commit -m "Testes unitários no modelo User"

Mudanças no negócio

E se quisermos que os campos nome e email sejam obrigatórios?

@jlucasps

Mudanças no negócioVamos 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 endend

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 seconds9 examples, 2 failuresFailed examples:rspec ./spec/models/user_spec.rb:14 # User fail to create a user when name is blankrspec ./spec/models/user_spec.rb:19 # User fail to create a user when email is blankRandomized with seed 46469

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

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 endendjlucasps@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) ==========================

Mudanças no negócio

@jlucasps

class User < ActiveRecord::Base # Attrs accessible attr_accessible :name, :email, :age # Validations validates :name, :presence => true, :allow_blank => false validates :email, :presence => true, :allow_blank => false # Associations # Scopes # Públic methods end

Mudanças no negócio

@jlucasps

jlucasps@lotus:/media/truecrypt1/first_app$ rake db:test:loadjlucasps@lotus:/media/truecrypt1/first_app$ rspec......... Finished in 0.50004 seconds9 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."

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órioQuais os passos?

@jlucasps

Testes Código Refactoring

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 endend

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 seconds11 examples, 2 failuresFailed examples:rspec ./spec/models/user_spec.rb:24 # User fail to create a user with gender value MALErspec ./spec/models/user_spec.rb:29 # User fail to create a user with gender value FEMALERandomized with seed 23755

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

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 endend

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 seconds11 examples, 0 failuresRandomized with seed 52935

$ git status$ git add .$ git commit -m "Adicionando campo gender no modelo User."

Mudanças no negócioE 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 endcontext "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

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 seconds16 examples, 1 failureFailed examples:rspec ./spec/models/user_spec.rb:46 # User when age >= 18 does not create an user without gender value

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

Mudanças no negócio

@jlucasps

jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec................

Finished in 0.40208 seconds16 examples, 0 failures

Randomized with seed 46088

$ git status$ git add .$ git commit -m "Necessário informar o sexo caso a idade seja maior ou igual a 18."

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.

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

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)>'

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

Mudanças no negócio

@jlucasps

Atualizando o model Userclass 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 endend

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 seconds22 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 emailsRandomized with seed 45305

Agora sim, encontramos o erro esperado na criação de dois usuários com mesmo email.

Mudanças no negócio

@jlucasps

first_app$ rails g migration AddUniqueToEmail

class AddUniqueToEmail < ActiveRecord::Migration def change add_index :users, :email, :unique => true endendjlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:migrate== AddUniqueToEmail: migrating ===============================================-- add_index(:users, :email, {:unique=>true}) -> 0.0009s== AddUniqueToEmail: migrated (0.0010s) ========================================

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

Mudanças no negócio

@jlucasps

jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec...................... Finished in 0.46128 seconds22 examples, 0 failures Randomized with seed 64622

$ git status$ git add .$ git commit -m "Validação de unicidade de email."

Mudanças no negócio

@jlucasps

Outras possíveis validações:● formatos● inclusão em uma lista, range● exclusão● formato● presença● números (inteiros, decimais)● condicionais● custom validations

Desenvolvimento Web com Ruby on

Rails

João Lucas Pereira de Santanagtalk | linkedin | twitter: jlucasps

Obrigado!