Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Desenvolvimento Web com Ruby on Rails (parte 3)

Desenvolvimento Web com Ruby on Rails (parte 3)

Desenvolvimento Web com Ruby on Rails

More Decks by João Lucas Pereira de Santana

Other Decks in Education

Transcript

  1. Desenvolvimento Web com Ruby on Rails João Lucas Pereira de

    Santana gtalk | linkedin | twitter: jlucasps
  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
  3. 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 ?
  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
  9. 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)
  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
  12. 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) ======================================
  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 => "[email protected]", :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 => "[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
  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) ==========================
  22. 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
  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 => "[email protected]", :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 => "[email protected]", :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 => "[email protected]", :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 => "[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
  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
  33. 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."
  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 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
  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 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 <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 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 <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 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
  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
  42. 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."
  43. 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
  44. Desenvolvimento Web com Ruby on Rails João Lucas Pereira de

    Santana gtalk | linkedin | twitter: jlucasps Obrigado!