Slide 1

Slide 1 text

Desenvolvimento Web com Ruby on Rails João Lucas Pereira de Santana gtalk | linkedin | twitter: jlucasps

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Definindo Models @jlucasps users name email age users id name email age created_at updated_at Como criar a tabela no banco de dados utilizando o Rails ?

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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)

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Migrations Mé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)

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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 => "[email protected]", :age => 24) user.save.should be_true end end

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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"

Slide 17

Slide 17 text

Mudanças no negócio E se quisermos que os campos nome e email sejam obrigatórios? @jlucasps

Slide 18

Slide 18 text

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 => "[email protected]", : age => 24) user.save.should be_true end it "fail to create a user when name is blank" do user = User.new(:email => "[email protected]", :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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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."

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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 => "[email protected]", :age => 24, :gender => User::FEMALE) user.save.should be_true end end

Slide 26

Slide 26 text

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 => "[email protected]", :age => 72, :gender => User::MALE) NameError: uninitialized constant User::MALE # ./spec/models/user_spec.rb:25:in `block (2 levels) in ' 2) User fail to create a user with gender value FEMALE Failure/Error: user = User.new(:name => "Candice Swanepoel", :email => "[email protected]", :age => 24, :gender => User::FEMALE) NameError: uninitialized constant User::FEMALE # ./spec/models/user_spec.rb:30:in `block (2 levels) in ' 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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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."

Slide 30

Slide 30 text

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 => "[email protected]", :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 => "[email protected]", :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 => "[email protected]", :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 => "[email protected]", :age => 17) user.save.should be_true end end

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Mudanças no negócio @jlucasps jlucasps@lotus: /media/truecrypt1/handsonrails/first_app$ rspec ................ Finished in 0.40208 seconds 16 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."

Slide 34

Slide 34 text

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.

Slide 35

Slide 35 text

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 protected]") user_2 = User.new(:name => "Segundo usuário", :email => "[email protected]") 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 protected]") user_2 = User.new(:name => "Segundo usuário", :email => "[email protected]") user_2.save.should be_false end end

Slide 36

Slide 36 text

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 protected]") NoMethodError: undefined method `>=' for nil:NilClass # ./app/models/user.rb:21:in `adulthood' # ./spec/models/user_spec.rb:68:in `block (3 levels) in ' 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 protected]") NoMethodError: undefined method `>=' for nil:NilClass # ./app/models/user.rb:21:in `adulthood' # ./spec/models/user_spec.rb:74:in `block (3 levels) in '

Slide 37

Slide 37 text

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 protected]", :age => 18) user.adulthood.should be_true end it "is adult when age > 18" do user = User.new(:name => "Nome", :email => "[email protected]", :age => 30) user.adulthood.should be_true end it "is not adult when age < 18" do user = User.new(:name => "Nome", :email => "[email protected]", :age => 17) user.adulthood.should be_false end it "is not adult when age is blank" do user = User.new(:name => "Nome", :email => "[email protected]") user.adulthood.should be_false end end

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Mudanças no negócio @jlucasps jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rspec ...................... Finished in 0.46128 seconds 22 examples, 0 failures Randomized with seed 64622 $ git status $ git add . $ git commit -m "Validação de unicidade de email."

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Desenvolvimento Web com Ruby on Rails João Lucas Pereira de Santana gtalk | linkedin | twitter: jlucasps Obrigado!