Impacta - Show Day de Rails

67 %
33 %
Information about Impacta - Show Day de Rails
Technology

Published on November 29, 2008

Author: akitaonrails

Source: slideshare.net

Description

Apostilha do mini-curso ministrado na Impacta sobre Ruby e Rails.

Show Day Test Drive Ruby on Rails com Fabio Akita AkitaOnRails.com • Mais conhecido pelo blog AkitaOnRails.com e pelo Rails Brasil Podcast (podcast.rubyonrails.pro.br) • Escritor do primeiro livro de Rails em português: “Repensando a Web com Rails” • Revisor Técnico da recém-lançada tradução de “Desenvolvimento Web Ágil com Rails” • Trabalhou um ano como Rails Practice Manager para a consultoria americana Surgeworks LLC • Atualmente é Gerente de Produtos Rails na Locaweb

Introdução Ruby • Criado por Yukihiro Matsumoto (Matz) • Desde 1993 • “Ruby” inspirado por “Perl”, Python, Smalltalk • Livro “Programming Ruby” (PickAxe) por Dave Thomas, The Pragmatic Programmer • “MRI” (Matz Ruby Interpretor)

Ruby • Linguagem “Humana” • Linguagem Dinâmica • Princípio da Menor Surpresa • Quase totalmente orientada a objetos • Multi-paradigma (Funcional, Imperativa, Reflexiva, Objetos, etc) • Interpretada (até 1.8) Instalação • Mac (Leopard) - pré-instalado • Linux (Ubuntu) - apt-get • Windows - One-Click Installer

Mac OS X Leopard • Atualizar para versões mais recentes: • sudo gem update --system • sudo gem update • Instalar MacPorts (macports.org) Ubuntu 8.04 • apt-get para instalar ruby • baixar tarball rubygems • gem install rails

Ubuntu 8.04 sudo apt-get install ruby irb ri rdoc ruby1.8-dev libzlib-ruby libyaml-ruby libreadline-ruby libncurses-ruby libcurses-ruby libruby libruby-extras libfcgi-ruby1.8 build-essential libopenssl-ruby libdbm-ruby libdbi-ruby libdbd-sqlite3-ruby sqlite3 libsqlite3-dev libsqlite3- ruby libxml-ruby libxml2-dev wget http://rubyforge.org/frs/download.php/38646/rubygems-1.2.0.tgz tar xvfz rubygems-1.2.0.tgz cd rubygems-1.2.0 sudo ruby setup.rb sudo ln -s /usr/bin/gem1.8 /usr/bin/gem sudo gem install rails sqlite3-ruby mongrel capistrano Windows • Baixar One-Click Installer • gem install rails

Windows • gem install RubyInline • FreeImage • freeimage.sourceforge.net/download.html • copiar FreeImage.dll no c:windows system32 • mmediasys.com/ruby (image_science) RubyGems • gem update --system • gem install rubygems-update • update_rubygems • gem install rails --version=2.0.2 • gem list • gem uninstall rails --version=1.2.6

Ferramentas • Subversion • Ubuntu - apt-get install subversion • Mac - port install subversion • Windows - http://subversion.tigris.org/getting.html#windows • Git • Ubuntu - apt-get install git-core git-svn • Mac - port install git-core +svn • Windows - http://code.google.com/p/msysgit/ Ferramentas • MySQL 5 (banco de dados relacional) • Ubuntu - apt-get install mysql-server mysql-client libdbd-mysql-ruby libmysqlclient15-dev • Mac - port install mysql5 +server • Windows - http://dev.mysql.com/downloads/mysql/5.0.html

Ferramentas • ImageMagick (processador de imagens) • Ubuntu - apt-get install libmagick9-dev • Mac - port install tiff -macosx imagemagick +q8 +gs +wmf • Windows - (rmagick-win32) http://rubyforge.org/projects/rmagick/ Editores • Windows - Scite, UltraEdit, Notepad++ • Ubuntu - gEdit, Emacs,Vi • Mac - TextMate (macromates.com) • Qualquer um server - Aptana, Netbeans

IRB • Interpreted Ruby • Shell interativo que executa qualquer comando Ruby [20:42][~]$ irb >> 1 + 2 => 3 >> class Foo; end => nil >> f = Foo.new => #<Foo:0x11127e4> >> Aprendendo Ruby Adaptado de “10 Things Every Java Programmer Should Know” por Jim Weinrich

“é fácil escrever Fortran em qualquer linguagem” Jim Weinrich “uma linguagem que não afeta seu jeito de programar não vale a pena aprender” Alan Perlis

Convenções • NomesDeClasse • nomes_de_metodos e nomes_de_variaveis • metodos_fazendo_pergunta? • metodos_perigosos! • @variaveis_de_instancia • $variaveis_globais • ALGUMAS_CONSTANTES ou OutrasConstantes Convenções class MinhaClasse < ClassePai def hello_world(nome) return if nome.empty? $WORLD = quot;WORLD quot; monta_frase(nome) class ClassePai @frase.upcase! HELLO = quot;Hello quot; puts @frase end end def monta_frase(nome) @frase = HELLO + $WORLD + nome end end >> obj = MinhaClasse.new => #<MinhaClasse:0x10970e4> >> obj.hello_world quot;Fabioquot; HELLO WORLD FABIO

Estruturas class MinhaClasse < ClassePai def self.metodo_de_classe quot;nao é a mesma coisa que estáticoquot; end def metodo_de_instancia if funciona? ... while true case @teste break if completo? when quot;1quot; end quot;primeira condicaoquot; else when quot;2quot; return quot;não funcionaquot; quot;segunda condicaoquot; end else return unless completo? quot;condicao padraoquot; @teste = quot;1quot; if funciona? end ... end end Arrays e Strings >> a = [1,2,3,4] >> hello = quot;Helloquot; => [1, 2, 3, 4] => quot;Helloquot; >> b = [quot;umquot;, quot;doisquot;, quot;tresquot;] >> a = hello + ' Fulano' => [quot;umquot;, quot;doisquot;, quot;tresquot;] => quot;Hello Fulanoquot; >> c = %w(um dois tres) >> b = quot;#{hello} Fulanoquot; => [quot;umquot;, quot;doisquot;, quot;tresquot;] => quot;Hello Fulanoquot; >> c = <<-EOF multiplas linhas EOF => quot; multiplasn linhasnquot; >>

Arrays e Strings >> lista_longa = [<<FOO, <<BAR, <<BLATZ] teste1 teste2 FOO foo1 foo2 BAR alo1 alo2 BLATZ => [quot;teste1nteste2nquot;, quot;foo1nfoo2nquot;, quot;alo1nalo2nquot;] Apenas curiosidade. Não se costuma fazer isso. Carregar require 'activesupport' @teste = 1.gigabyte / 1.megabyte puts @teste.kilobyte • require • carrega arquivos .rb relativos a onde se está • carrega gems • pode ser passado um caminho (path) absoluto • carrega apenas uma vez (load carrega repetidas vezes) • não há necessidade do nome da classe ser a mesma que o nome do arquivo

Rubismos • Parênteses não obrigatórios • Argumentos se comportam como Arrays • não precisa de “return” • Arrays podem ser representados de diversas formas • Strings podem ser representados de diversas formas “Splat” def foo(*argumentos) arg1, *outros = argumentos [arg1, outros] end >> foo(1, 2, 3, 4) => [1, [2, 3, 4]] Joga uma lista de objetos >> a,b,c = 1,2,3 => [1, 2, 3] em um Array >> *a = 1,2,3,4 => [1, 2, 3, 4]

Strings e Symbols >> quot;testequot;.object_id => 9417020 >> quot;testequot;.object_id => 9413000 •String são mutáveis >> :teste.object_id •Symbols são imutáveis => 306978 >> :teste.object_id => 306978 •Symbols são comumente usados como chaves >> :teste.to_s.object_id => 9399150 >> :teste.to_s.object_id => 9393460 Hashes >> html = { :bgcolor => quot;blackquot;, :body => { :margin => 0, :width => 100 } } >> html[:bgcolor] => quot;blackquot; >> html[:body][:margin] => 0

Tudo é Objeto >> 1.class >> true.class >> Class.class => Fixnum => TrueClass => Class >> quot;aquot;.class >> nil.class >> {}.class => String => NilClass => Hash >> (1.2).class >> Array.class >> [].class => Float => Class => Array Tudo é Objeto >> Array.class >> a.class => Class => Array >> MeuArray = Array >> b.class => Array => Array >> a = Array.new(2) >> b.is_a? MeuArray => [nil, nil] => true >> b = MeuArray.new(3) >> a.is_a? MeuArray => [nil, nil, nil] => true Toda Classe é um objeto, instância de Class

Tudo é Objeto Não há “primitivas” def fabrica(classe) classe.new >> 1 + 2 end => 3 >> 4.* 3 >> 1.+(2) => 12 >> fabrica(Array) => 3 >> 4 * 3 => [] >> 3.-(1) => 12 >> fabrica(String) => 2 => quot;quot; Classe é um Operações aritméticas são objeto métodos de Fixnum Não Objetos >> foo = quot;testequot; • Nomes de variáveis => >> quot;testequot; a = foo não são objetos => quot;testequot; • Variáveis não >> => b = a quot;testequot; costumam ter referência a outros >> foo.object_id => 8807330 objetos >> a.object_id • Blocos (mais => >> 8807330 b.object_id adiante) => 8807330

Quase tudo são Mensagens • Toda a computação de Ruby acontece através de: • Ligação de nomes a objetos (a = b) • Estruturas primitivas de controle (if/ else,while) e operadores (+, -) • Enviando mensagens a objetos Mensagens • obj.metodo() • Java: “chamada” de um método • obj.mensagem • Ruby: envio de “mensagens” • pseudo: obj.enviar(“mensagem”)

Mensagens >> 1 + 2 => 3 Envio da mensagem “+” ao objeto “1” >> 1.+ 2 com parâmetro “2” => 3 >> quot;testequot;.size Envio da mensagem => 5 “size” ao objeto “teste” Mensagens method_missing irá class Pilha interceptar toda mensagem attr_accessor :buffer não definida como método def initialize @buffer = [] end def method_missing(metodo, *args, &bloco) @buffer << metodo.to_s end end

Mensagens >> pilha = Pilha.new => #<Pilha:0x1028978 @buffer=[]> >> pilha.blabla => [quot;blablaquot;] >> pilha.alo => [quot;blablaquot;, quot;aloquot;] >> pilha.hello_world => [quot;blablaquot;, quot;aloquot;, quot;hello_worldquot;] Meta-programação • Ruby: Herança Simples • Módulos: • permite “emular” herança múltipla sem efeitos colaterais • organiza código em namespaces • não pode ser instanciado

Classes Abertas class Fixnum def par? (self % 2) == 0 abrindo a classe padrão Fixnum e acrescentando um end novo método end >> p (1..10).select { |n| n.par? } # => [2, 4, 6, 8, 10] Mixins Fixnum.class_eval do include(Akita::MeuInteiro) end module Akita module MeuInteiro # ou def par? class Fixnum (self % 2) == 0 include Akita::MeuInteiro end end end end # ou Fixnum.send(:include, Akita::MeuInteiro)

Mixins class Pessoa >> carlos = Pessoa.new(quot;Carlosquot;, 20) include Comparable => #<Pessoa:0x103833c> attr_accessor :nome, :idade >> ricardo = Pessoa.new(quot;Ricardoquot;, 30) def initialize(nome, idade) => #<Pessoa:0x1033abc> self.nome = nome self.idade = idade >> carlos > ricardo end => false def <=>(outro) >> carlos == ricardo self.idade <=> outro.idade => false end >> carlos < ricardo end => true Métodos Singleton class Cachorro def rover.fale end puts quot;Rover Vermelhoquot; end rover = Cachorro.new fido = Cachorro.new rover.instance_eval do def fale puts quot;Rover vermelhoquot; end >> rover.fale end Rover Vermelho >> fido.fale NoMethodError: undefined method `fale' for #<Cachorro:0x10179e8> from (irb):90

Geração de Código class Module def trace_attr(sym) self.module_eval %{ class Cachorro def #{sym} trace_attr :nome printf quot;Acessando %s com valor %snquot;, def initialize(string) quot;#{sym}quot;, @#{sym}.inspect @nome = string end end } end end end >> Cachorro.new(quot;Fidoquot;).nome # => Acessando nome com valorquot;Fidoquot; Acessando nome com valor quot;Fidoquot; Geração de Código class Person def initialize(options = {}) @name = options[:name] @address = options[:address] @likes = options[:likes] end def name; @name; end def name=(value); @name = value; end def address; @address; end def address=(value); @address = value; end def likes; @likes; end def likes=(value); @likes = value; end end Tarefa chata! (repetitiva)

Geração de Código def MyStruct(*keys) Class.new do attr_accessor *keys def initialize(hash) hash.each do |key, value| instance_variable_set(quot;@#{key}quot;, value) end end end end Filosofia “Don’t Repeat Yourself” Geração de Código Person = MyStruct :name, :address, :likes dave = Person.new(:name => quot;davequot;, :address => quot;TXquot;, :likes => quot;Stiltonquot;) chad = Person.new(:name => quot;chadquot;, :likes => quot;Jazzquot;) chad.address = quot;COquot; >> puts quot;O nome do Dave e #{dave.name}quot; O nome do Dave e dave => nil >> puts quot;Chad mora em #{chad.address}quot; Chad mora em CO

Dynamic Typing Static Dynamic Weak Strong Dynamic Typing Static/Strong Java Dynamic/Weak Javascript Dynamic/”Strong” Ruby

“Strong” Typing class Parent def hello; puts quot;In parentquot;; end end class Child < Parent def hello; puts quot;In childquot; ; end end >> c = Child.new => #<Child:0x1061fac> >> c.hello In child “Strong Typing” class Child # remove quot;helloquot; de Child, mas ainda chama do Parent remove_method :hello end >> c.hello In parent class Child undef_method :hello # evita chamar inclusive das classes-pai end >> c.hello NoMethodError: undefined method `hello' for #<Child:0x1061fac> from (irb):79

Duck Typing • Se anda como um pato • Se fala como um pato • Então deve ser um pato • Compilação com tipos estáticos NÃO garante “código sem erro” • Cobertura de testes garante “código quase sem erro”. Compilação não exclui testes. Duck Typing class Gato def fala; quot;miauquot;; end end class Cachorro def fala; quot;au auquot;; end end for animal in [Gato.new, Cachorro.new] puts animal.class.name + quot; fala quot; + animal.fala end # Gato fala miau # Cachorro fala au au

Chicken Typing class Gato; def fala; quot;miauquot;; end; end class Cachorro; def fala; quot;au auquot;; end; end class Passaro; def canta; quot;piuquot;; end; end def canil(animal) return quot;Animal mudoquot; unless animal.respond_to?(:fala) return quot;Nao e cachorroquot; unless animal.is_a?(Cachorro) puts animal.fala end >> canil(Gato.new) => quot;Nao e cachorroquot; >> canil(Passaro.new) => quot;Animal mudoquot; >> canil(Cachorro.new) => au au Evite coisas assim! Blocos e Fechamentos lista = [1, 2, 3, 4, 5] for numero in lista puts numero * 2 loop tradicional end lista.each do |numero| puts numero * 2 loop com bloco end # mesma coisa: lista.each { |numero| puts numero * 2 }

Blocos e Fechamentos # jeito quot;antigoquot; f = nil begin f = File.open(quot;teste.txtquot;, quot;rquot;) texto = f.read ensure f.close end # com fechamentos File.open(quot;teste.txtquot;, quot;rquot;) do |f| texto = f.read end Blocos e Fechamentos # com yield def foo >> foo { puts quot;aloquot; } yield => alo end # como seria em quot;runtimequot; # como objeto def foo def foo(&block) puts quot;aloquot; block.call end end

Construções Funcionais >> (1..10).class => Range >> (1..10).map { |numero| numero * 2 } => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] >> (1..10).inject(0) { |numero, total| total += numero } => 55 >> (1..10).select { |numero| numero % 2 == 0 } => [2, 4, 6, 8, 10] Construções Funcionais “Dada uma coleção de números de 1 a 50, qual a soma de todos os números pares, cada qual multiplicado por 2?” lista = [] total = 0 numero = 1 for numero in lista while numero < 51 if numero % 2 == 0 lista << numero total += (numero * 2) numero += 1 end end end => 1300

Construções Funcionais >> (1..50).select { |n| n % 2 == 0 }.map { |n| n * 2 }.inject(0) { |n, t| t += n } => 1300 (1..50).select do |n| n % 2 == 0 (1..50).select { |n| end.map do |n| n % 2 == 0 }.map { |n| n * 2 n * 2 }.inject(0) { |n, t| end.inject(0) do |n, t| t += n } t += n end Tudo Junto def tag(nome, options = {}) if options.empty? puts quot;<#{nome}>quot; else attr = options.map { |k,v| quot;#{k}='#{v}' quot; } puts quot;<#{nome} #{attr}>quot; end puts yield if block_given? puts quot;</#{nome}>quot; end

Tudo Junto >> tag :div <div> </div> >> tag :img, :src => quot;logo.gifquot;, :alt => quot;imagemquot; <img alt='imagem' src='logo.gif' > </img> >> tag(:p, :style => quot;color: yellowquot;) { quot;Hello Worldquot; } <p style='color: yellow' > Hello World </p> Ruby on Rails “Domain Specific Language for the Web”

Ruby on Rails • Criado em 2004 por David Heinemeir Hansson • Extraído da aplicação Basecamp, da 37signals • “Convention over Configuration” • DRY: “Don’t Repeat Yourself ” • YAGNI: “You Ain’t Gonna Need It” • Metodologias Ágeis Convention over Configuration • Eliminar XMLs de configuração • As pessoas gostam de escolhas mas não gostam necessariamente de escolher • Escolhas padrão são “Limitações” • “Limitações” nos tornam mais produtivos • O computador tem que trabalhar por nós e não nós trabalharmos para o computador

DRY • A resposta de “por que Ruby?” • Meta-programação é a chave • Novamente: fazer o computador trabalhar para nós • DSL: “Domain Specific Languages” • RoR é uma DSL para a Web YAGNI • Se tentar suportar 100% de tudo acaba-se tendo 0% de coisa alguma • Pareto em Software: “80% do tempo é consumido resolvendo 20% dos problemas” • RoR tenta resolver 80% dos problemas da forma correta

Metodologias Ágeis • Martin Fowler: metodologias monumentais não resolvem o problema • Metodologias Ágeis: pragmatismo e qualidade de software • Integração Contínua, Cobertura de Testes, Automatização de tarefas, etc. • TDD: “Test-Driven Development” Tecnologias • Plugins: extendem as capacidades do Rails • Gems: bibliotecas Ruby, versionadas • RubyForge • Agile Web Development (plugins • Github

Complementos • BDD: Behaviour Driven Development (RSpec) • Automatização: Rake, Capistrano,Vlad • Application Servers: Mongrel, Thin, Ebb, Phusion Passenger • Web Servers: Apache 2, NginX, Lightspeed • Bancos de Dados: MySQL, PostgreSQL, SQLite3, Oracle, SQL Server, etc Iniciando um projeto Ruby on Rails Obs.: aprenda a apreciar a linha de comando!

[20:57][~/rails/sandbox/impacta]$ rails _2.1.0_ tarefas create create app/controllers create app/helpers opcional create app/models create app/views/layouts create config/environments create config/initializers ... create doc/README_FOR_APP create log/server.log create log/production.log create log/development.log create log/test.log Estrutura Padrão • app - estrutura MVC • config - configurações • public - recursos estáticos (imagens, CSS, javascript, etc) • script - ferramentas • test - suíte test/unit • vendor - plugins, gems, etc

Pacotes ActiveResource Rails Aplicação Rails ActiveSupport ActionController Mongrel ActionPack ActiveRecord Ruby ActiveWS ActionView ActionMailer Algumas convenções • Nomes no Plural • Quando se fala de uma coleção de dados (ex. nome de uma tabela no banco) • Nomes do Singular • Quando se fala de uma única entidade (ex. uma linha no banco • Rails usa Chaves Primárias Surrogadas (id inteiro) • Foreign Key é o nome da tabela associada no singular com “_id” (ex. usuario_id)

Configurações • database.yml • configuração de banco de dados • environment.rb • configurações globais • environments • configurações por ambiente • initializers • organização de configurações • routes.rb Ambientes Separados # SQLite version 3.x # gem install sqlite3-ruby (not necessary on OS X Leopard) development: adapter: sqlite3 database: db/development.sqlite3 timeout: 5000 # Warning: The database defined as quot;testquot; will be erased and # re-generated from your development database when you run quot;rakequot;. # Do not set this db to the same as development or production. test: adapter: sqlite3 database: db/test.sqlite3 timeout: 5000 production: adapter: sqlite3 database: db/production.sqlite3 timeout: 5000

Ambientes Separados • Development • Tudo que é modificado precisa recarregar imediatamente, cache tem que ser desativado • Permitido banco de dados com sujeira • Test • Todos os testes precisam ser repetitíveis em qualquer ambiente, por isso precisa de um banco de dados separado • O banco precisa ser limpo a cada teste. Cada teste precisa ser isolado. • Production • Otimizado para performance, as classes só precisam carregar uma única vez • Os sistemas de caching precisam ficar ligados YAML • “Yet Another Markup Language” • “YAML Ain’t a Markup Language” • Formato humanamente legível de serialização de estruturas de dados • Muito mais leve e prático que XML • JSON é quase um subset de YAML • Identação é muito importante!

Plugins: aceleradores >> ./script/plugin install git://github.com/technoweenie/restful-authentication.git removing: /Users/akitaonrails/rails/sandbox/impacta/tarefas/vendor/plugins/restful- authentication/.git Initialized empty Git repository in tarefas/vendor/plugins/restful-authentication/.git/ remote: Counting objects: 409, done. remote: Compressing objects: 100% (259/259), done. remote: Total 409 (delta 147), reused 353 (delta 115) Receiving objects: 100% (409/409), 354.52 KiB | 124 KiB/s, done. Resolving deltas: 100% (147/147), done. ... >> ./script/plugin install git://github.com/tapajos/brazilian-rails.git >> rake brazilianrails:inflector:portuguese:enable >> ./script/plugin install git://github.com/lightningdb/activescaffold.git >> ./script/generate authenticated Usuario Sessao Rake (Ruby Make) >> rake -T (in /Users/akitaonrails/rails/sandbox/impacta/tarefas) /Users/akitaonrails/rails/sandbox/impacta/tarefas/vendor/plugins/brazilian-rails/tasks rake brazilianrails:inflector:portuguese:check # Checks if Brazilian Por... rake brazilianrails:inflector:portuguese:disable # Disable Brazilian Portu... rake brazilianrails:inflector:portuguese:enable # Enable Brazilian Portug... rake db:abort_if_pending_migrations # Raises an error if ther... rake db:charset # Retrieves the charset f... rake db:collation # Retrieves the collation... rake db:create # Create the database def... ... rake tmp:pids:clear # Clears all files in tmp... rake tmp:sessions:clear # Clears all files in tmp... rake tmp:sockets:clear # Clears all files in tmp... Executa tarefas automatizadas, como limpeza de logs, gerenciamento do banco de dados, execução dos testes, etc.

Rake (Ruby Make) >> rake db:create:all db/development.sqlite3 already exists db/production.sqlite3 already exists db/test.sqlite3 already exists >> rake db:migrate == 20080629001252 CreateUsuarios: migrating =================================== -- create_table(quot;usuariosquot;, {:force=>true}) -> 0.0042s -- add_index(:usuarios, :login, {:unique=>true}) -> 0.0034s == 20080629001252 CreateUsuarios: migrated (0.0085s) ========================== >> rake Started ............. Finished in 0.340325 seconds. 13 tests, 26 assertions, 0 failures, 0 errors Started .............. Finished in 0.306186 seconds. 14 tests, 26 assertions, 0 failures, 0 errors Migrations >> ./script/generate scaffold Tarefa usuario:references duracao:integer descricao:string data_inicio:datetime exists app/models/ ... create db/migrate/20080629003332_create_tarefas.rb class CreateTarefas < ActiveRecord::Migration def self.up create_table :tarefas do |t| t.references :usuario t.integer :duracao t.string :descricao t.datetime :data_inicio t.timestamps end end def self.down drop_table :tarefas end end

Migrations >> rake db:migrate == 20080629001252 CreateUsuarios: migrating =================================== -- create_table(quot;usuariosquot;, {:force=>true}) -> 0.0047s -- add_index(:usuarios, :login, {:unique=>true}) -> 0.0039s == 20080629001252 CreateUsuarios: migrated (0.0092s) ========================== == 20080629003332 CreateTarefas: migrating ==================================== -- create_table(:tarefas) -> 0.0039s == 20080629003332 CreateTarefas: migrated (0.0044s) =========================== Migrations CREATE TABLE quot;schema_migrationsquot; (quot;versionquot; varchar(255) NOT NULL) CREATE UNIQUE INDEX quot;unique_schema_migrationsquot; ON quot;schema_migrationsquot; (quot;versionquot;) Migrating to CreateUsuarios (20080629001252) SELECT version FROM schema_migrations CREATE TABLE quot;usuariosquot; (quot;idquot; INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, quot;loginquot; varchar(40) DEFAULT NULL NULL, quot;namequot; varchar(100) DEFAULT '' NULL, quot;emailquot; varchar(100) DEFAULT NULL NULL, quot;crypted_passwordquot; varchar(40) DEFAULT NULL NULL, quot;saltquot; varchar(40) DEFAULT NULL NULL, quot;created_atquot; datetime DEFAULT NULL NULL, quot;updated_atquot; datetime DEFAULT NULL NULL, quot;remember_tokenquot; varchar(40) DEFAULT NULL NULL, quot;remember_token_expires_atquot; datetime DEFAULT NULL NULL) CREATE UNIQUE INDEX quot;index_usuarios_on_loginquot; ON quot;usuariosquot; (quot;loginquot;) INSERT INTO schema_migrations (version) VALUES ('20080629001252') Migrating to CreateTarefas (20080629003332) SELECT version FROM schema_migrations CREATE TABLE quot;tarefasquot; (quot;idquot; INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, quot;usuario_idquot; integer DEFAULT NULL NULL, quot;duracaoquot; integer DEFAULT NULL NULL, quot;descricaoquot; varchar(255) DEFAULT NULL NULL, quot;data_inicioquot; datetime DEFAULT NULL NULL, quot;created_atquot; datetime DEFAULT NULL NULL, quot;updated_atquot; datetime DEFAULT NULL NULL) INSERT INTO schema_migrations (version) VALUES ('20080629003332') SELECT version FROM schema_migrations

Migrations • Versionamento do Schema do Banco de Dados • O Schema completo fica em db/schema.rb • Possibilita pseudo-”rollback” e, efetivamente, mais controle entre versões • Garante que os diferentes ambientes sempre estejam consistentes • Evita conflitos em times com mais de 2 desenvolvedores • Aumenta muito a produtividade Servidor >> ./script/server => Booting Mongrel (use 'script/server webrick' to force WEBrick) => Rails 2.1.0 application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server ** Starting Mongrel listening at 0.0.0.0:3000 ** Starting Rails with development environment... ** Rails loaded. ** Loading any Rails specific GemPlugins ** Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart). ** Rails signals registered. HUP => reload (without restart). It might not work well. ** Mongrel 1.1.5 available at 0.0.0.0:3000 ** Use CTRL-C to stop. Se não houver Mongrel instalado, ele sobe Webrick (não recomendado)

Servidor não esquecer de apagar public/index.html M.V.C. Model-View-Controller feito direito

requisição HTTP ! Mongrel Mongrel ! routes.rb routes.rb ! Controller Controller ! Action Action ! Model Action ! View Action ! resposta HTTP

Controllers # app/controllers/application.rb class ApplicationController < ActionController::Base helper :all # include all helpers, all the time protect_from_forgery end # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController # GET /tarefas # GET /tarefas.xml def index @tarefas = Tarefa.find(:all) respond_to do |format| format.html # index.html.erb format.xml { render :xml => @tarefas } end end end Views - Templates <!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Transitional//ENquot; quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtdquot;> <html xmlns=quot;http://www.w3.org/1999/xhtmlquot; xml:lang=quot;enquot; lang=quot;enquot;> <head> <meta http-equiv=quot;content-typequot; content=quot;text/html;charset=UTF-8quot; /> <title>Tarefas: <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> </head> <body> <p style=quot;color: greenquot;><%= flash[:notice] %></p> <%= yield %> </body> </html>

Views - Templates • app/views/layouts • application.html.erb • controller_name.html.erb • app/views/controller_name • action_name.html.erb • action_name.mime_type.engine • mime-type: html, rss, atom, xml, pdf, etc • engine: erb, builder, etc Models # app/models/usuario.rb require 'digest/sha1' class Usuario < ActiveRecord::Base include Authentication include Authentication::ByPassword include Authentication::ByCookieToken validates_presence_of :login validates_length_of :login, :within => 3..40 validates_uniqueness_of :login, :case_sensitive => false validates_format_of :login, :with => RE_LOGIN_OK, :message => MSG_LOGIN_BAD ... end

Rotas # config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :tarefas map.logout '/logout', :controller => 'sessoes', :action => 'destroy' map.login '/login', :controller => 'sessoes', :action => 'new' map.register '/register', :controller => 'usuarios', :action => 'create' map.signup '/signup', :controller => 'usuarios', :action => 'new' map.resources :usuarios map.resource :sessao # Install the default routes as the lowest priority. map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' jeito “antigo” (1.2) end Rotas - Antigo • http://www.dominio.com/tarefas/show/123 • map.connect ':controller/:action/:id' • tarefas_controller.rb ! TarefasController • def show ... end • params[:id] ! 123

Active Record

• “Patterns of Enterprise Application Architecture”, Martin Fowler • Uma classe “Model” mapeia para uma tabela • Uma instância da classe “Model” mapeia para uma linha na tabela • Toda lógica de negócio é implementada no “Model” • Suporte para Herança Simples, Polimorfismo, Associações Associações Simples # app/models/tarefa.rb class Tarefa < ActiveRecord::Base belongs_to :usuario # uma tarefa pertence a um usuario end # app/models/usuario.rb class Usuario < ActiveRecord::Base has_many :tarefas # um usuario tem varias tarefas ... end tarefas usuarios id: integer id: integer usuario_id: integer

Interatividade - Console >> Usuario.count => 0 >> Tarefa.count => 0 >> admin = Usuario.create(:login => 'admin', :password => 'admin', :password_confirmation => 'admin', :email => 'admin@admin.com') => #<Usuario id: nil, login: quot;adminquot;, name: quot;quot;, email: quot;admin@admin.comquot;, crypted_password: nil, salt: nil, created_at: nil, updated_at: nil, remember_token: nil, remember_token_expires_at: nil> >> Usuario.count => 0 >> admin.errors.full_messages => [quot;Password deve ter no minimo 6 caractere(s)quot;] Interatividade - Console >> admin = Usuario.create(:login => 'admin', :password => 'admin123', :password_confirmation => 'admin123', :email => 'admin@admin.com') => #<Usuario id: 1, login: quot;adminquot;, name: quot;quot;, email: quot;admin@admin.comquot;, crypted_password: quot;e66...abdquot;, salt: quot;731...b96quot;, created_at: quot;2008-06-29 20:21:10quot;, updated_at: quot;2008-06-29 20:21:10quot;, remember_token: nil, remember_token_expires_at: nil> >> admin.tarefas => [] >> admin.tarefas.create(:descricao => quot;Criando demo de Railsquot;, :duracao => 2, :data_inicio => Time.now) => #<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: quot;Criando demo de Railsquot;, data_inicio: quot;2008-06-29 20:21:40quot;, created_at: quot;2008-06-29 20:21:40quot;, updated_at: quot;2008-06-29 20:21:40quot;>

Interatividade - Console >> Tarefa.count => 1 >> tarefa = Tarefa.first => #<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: quot;Criando demo de Railsquot;, data_inicio: quot;2008-06-29 20:21:40quot;, created_at: quot;2008-06-29 20:21:40quot;, updated_at: quot;2008-06-29 20:21:40quot;> >> tarefa.usuario => #<Usuario id: 1, login: quot;adminquot;, name: quot;quot;, email: quot;admin@admin.comquot;, crypted_password: quot;e66...abdquot;, salt: quot;731...b96quot;, created_at: quot;2008-06-29 20:21:10quot;, updated_at: quot;2008-06-29 20:21:10quot;, remember_token: nil, remember_token_expires_at: nil> Validações # app/models/usuario.rb class Usuario < ActiveRecord::Base ... validates_presence_of :login validates_length_of :login, :within => 3..40 validates_uniqueness_of :login, :case_sensitive => false validates_format_of :login, :with => RE_LOGIN_OK, :message => MSG_LOGIN_BAD validates_format_of :name, :with => RE_NAME_OK, :message => MSG_NAME_BAD, :allow_nil => true validates_length_of :name, :maximum => 100 validates_presence_of :email validates_length_of :email, :within => 6..100 #r@a.wk validates_uniqueness_of :email, :case_sensitive => false validates_format_of :email, :with => RE_EMAIL_OK, :message => MSG_EMAIL_BAD ... end

Validações validates_presence_of :firstname, :lastname # obrigatório validates_length_of :password, :minimum => 8 # mais de 8 caracteres :maximum => 16 # menos que 16 caracteres :in => 8..16 # entre 8 e 16 caracteres :too_short => 'muito curto' :too_long => 'muito longo' validates_acceptance_of :eula # Precisa aceitar este checkbox :accept => 'Y' # padrão: 1 (ideal para checkbox) validates_confirmation_of :password # os campos password e password_confirmation precisam ser iguais validates_uniqueness_of :user_name # user_name tem que ser único :scope => 'account_id' # Condição: # account_id = user.account_id Validações validates_format_of :email # campo deve bater com a regex :with => /^(+)@((?:[-a-z0-9]+.)+[a-z]{2,})$/i validates_numericality_of :value # campos value é numérico :only_integer => true :allow_nil => true validates_inclusion_of :gender, # gender é m ou f (enumeração) :in => %w( m, f ) validates_exclusion_of :age # campo age não pode estar :in => 13..19 # entre 13 e 19 anos validates_associated :relation # valida que o objeto ‘relation’ associado também é válido

Finders Tarefa.find 42 # objeto com ID 42 Tarefa.find [37, 42] # array com os objetos de ID 37 e 42 Tarefa.find :all Tarefa.find :last Tarefa.find :first, :conditions => [ quot;data_inicio < ?quot;, Time.now ] # encontra o primeiro objeto que obedece à! condição Tarefa.all # idêntico à! Tarefa.find(:all) Tarefa.first # idêntico à! Tarefa.find(:first) Tarefa.last # idêntico à! Tarefa.find(:last) :order => 'data_inicio DESC'# ordenação :offset => 20 # começa a partir da linha 20 :limit => 10 # retorna apenas 10 linhas :group => 'name' # agrupamento :joins => 'LEFT JOIN ...' # espaço para joins, como LEFT JOIN (raro) :include => [:account, :friends] # LEFT OUTER JOIN com esses models # dependendo das condições podem ser # 2 queries :include => { :groups => { :members=> { :favorites } } } :select => [:name, :adress] # em vez do padrão SELECT * FROM :readonly => true # objetos não podem ser modificados Named Scopes # app/models/tarefa.rb class Tarefa < ActiveRecord::Base ... named_scope :curtas, :conditions => ['duracao < ?', 2] named_scope :medias, :conditions => ['duracao between ? and ?', 2, 6] named_scope :longas, :conditions => ['duracao > ?', 6] named_scope :hoje, lambda { { :conditions => ['data_inicio between ? and ?', Time.now.beginning_of_day, Time.now.end_of_day ] } } end

Named Scope >> Tarefa.hoje SELECT * FROM quot;tarefasquot; WHERE (data_inicio between '2008-06-29 00:00:00' and '2008-06-29 23:59:59') >> Tarefa.curtas SELECT * FROM quot;tarefasquot; WHERE (duracao < 2) >> Tarefa.medias.hoje SELECT * FROM quot;tarefasquot; WHERE ((data_inicio between '2008-06-29 00:00:00' and '2008-06-29 23:59:59') AND (duracao between 2 and 6)) Condições de SQL geradas de maneira automática Muito Mais • Associações complexas (many-to-many, self-referencial, etc) • Associações Polimórficas • Colunas compostas (composed_of) • extensões acts_as (acts_as_tree, acts_as_nested_set, etc) • Callbacks (filtros) • Transactions (não suporta two-phased commits), Lockings • Single Table Inheritance (uma tabela para múltiplos models em herança) • Finders dinâmicos, Named Scopes

RESTful Rails Rotas Restful # config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :tarefas end # app/views/tarefas/index.html.erb ... <td><%= link_to 'Edit', edit_tarefa_path(tarefa) %></td> # script/console >> app.edit_tarefa_path(123) => quot;/tarefas/123/editquot;

CRUD - SQL Create INSERT Read SELECT Update UPDATE Destroy DELETE CRUD - Antigo GET /tarefas index GET /tarefas/new new GET /tarefas/edit/1 edit GET /tarefas/show/1 show POST /tarefas/create create POST /tarefas/update update POST /tarefas/destroy destroy

Verbos HTTP GET POST PUT DELETE CRUD - REST - DRY GET /tarefas index POST /tarefas create GET /tarefas/new new GET /tarefas/1 show PUT /tarefas/1 update DELETE /tarefas/1 destroy GET /tarefas/1/edit edit

REST - Named Routes /tarefas tarefas_path /tarefas tarefas_path /tarefas/new new_tarefa_path /tarefas/1 tarefa_path(1) /tarefas/1 tarefa_path(1) /tarefas/1 tarefa_path(1) /tarefas/1/edit edit_tarefa_path(1) REST Controller # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController # GET /tarefas def index # GET /tarefas/1 def show # GET /tarefas/new def new # GET /tarefas/1/edit def edit # POST /tarefas def create # PUT /tarefas/1 def update # DELETE /tarefas/1 def destroy end

REST Templates # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController # GET /tarefas/new def new @tarefa = Tarefa.new respond_to do |format| format.html # new.html.erb format.xml { render :xml => @tarefa } end end <!-- /tarefas/1 --> ... <form action=quot;/tarefas/3quot; class=quot;edit_tarefaquot; end id=quot;edit_tarefa_3quot; method=quot;postquot;> <input name=quot;_methodquot; type=quot;hiddenquot; # app/views/tarefas/new.html.erb value=quot;putquot; /> <% form_for(@tarefa) do |f| %> ... ... <p> <p> <input id=quot;tarefa_submitquot; name=quot;commitquot; <%= f.submit quot;Createquot; %> type=quot;submitquot; value=quot;Createquot; /> </p> </p> <% end %> </form> Nested Controllers >> ./script/generate scaffold Anotacao tarefa:references anotacao:text >> rake db:migrate # app/models/tarefa.rb class Tarefa < ActiveRecord::Base belongs_to :usuario has_many :anotacoes end # app/models/anotacao.rb class Anotacao < ActiveRecord::Base belongs_to :tarefa end

Nested Controllers # app/controllers/anotacoes_controller.rb # trocar X class AnotacoesController < ApplicationController # por Y before_filter :load_tarefa ... Anotacao.find private @tarefa.anotacoes.find def load_tarefa Anotacao.new @tarefa = Tarefa.find(params[:tarefa_id]) @tarefa.anotacoes.build end end redirect_to(@anotacao) redirect_to([@tarefa, @anotacao]) # config/routes.rb :location => @anotacao ActionController::Routing::Routes.draw do |map| :location => [@tarefa, @anotacao] map.resources :tarefas, :has_many => :anotacoes ... anotacoes_url end tarefa_anotacoes_url(@tarefa) Nested Views # trocar X # por Y em todos os arquivos app/views/anotacoes/*.html.erb form_for(@anotacao) <!-- apagar de new.html.erb form_for([@tarefa, @anotacao]) e edit.html.erb --> <p> link_to 'Show', @anotacao <%= f.label :tarefa %><br /> link_to 'Show', [@tarefa, @anotacao] <%= f.text_field :tarefa %> </p> anotacoes_path tarefa_anotacoes_path(@tarefa) <!-- acrescentar ao final de edit_anotacao_path(@tarefa) index.html.erb --> edit_tarefa_anotacao_path(@tarefa, @anotacao) <%= link_to 'Back to Tarefa', tarefa_path(@tarefa) %> anotacoes_path tarefa_anotacoes_path(@tarefa) <!-- acrescentar ao final de tarefas/show.html.erb --> new_anotacao_path <%= link_to 'Anotações', new_tarefa_anotacao_path(@tarefa) tarefa_anotacoes_path(@tarefa) %>

Namespaced Routes >> mv app/helpers/usuarios_helper.rb app/helpers/usuarios_helper.rb.old >> ./script/generate controller Admin::Usuarios create app/controllers/admin create app/helpers/admin create app/views/admin/usuarios create test/functional/admin create app/controllers/admin/usuarios_controller.rb create test/functional/admin/usuarios_controller_test.rb create app/helpers/admin/usuarios_helper.rb >> mv app/helpers/usuarios_helper.rb.old app/helpers/usuarios_helper.rb # config/routes.rb ActionController::Routing::Routes.draw do |map| map.namespace :admin do |admin| admin.resources :usuarios, :active_scaffold => true end end Active Scaffold apague tudo em app/views/layouts e crie apenas este application.html.erb <!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Transitional//ENquot; quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtdquot;> <html xmlns=quot;http://www.w3.org/1999/xhtmlquot; xml:lang=quot;enquot; lang=quot;enquot;> <head> <meta http-equiv=quot;content-typequot; content=quot;text/html;charset=UTF-8quot; /> <title><%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> <%= javascript_include_tag :defaults %> <%= active_scaffold_includes %> </head> <body> <p style=quot;color: greenquot;><%= flash[:notice] %></p> <%= yield %> </body> </html>

Active Scaffold # app/controllers/admin/usuarios_controller.rb class Admin::UsuariosController < ApplicationController active_scaffold :usuario do |config| config.columns = [:login, :email, :password, :password_confirmation ] config.list.columns.exclude [ :password, :password_confirmation ] config.update.columns.exclude [ :login] end end Active Scaffold

RESTful Rails Parte 2: has many through many-to-many anotacoes # app/models/anotacao.rb class Anotacao < ActiveRecord::Base id: int belongs_to :tarefa end tarefa_id: int # app/models/tarefa.rb class Tarefa < ActiveRecord::Base belongs_to :usuario tarefas has_many :anotacoes ... id: int end usuario_id: int # app/models/usuario.rb class Usuario < ActiveRecord::Base ... has_many :tarefas usuarios has_many :anotacoes, :through => :tarefas ... end id: int

many-to-many >> admin = Usuario.find_by_login('admin') => #<Usuario id: 1, login: quot;adminquot;, name: quot;quot;, email: quot;admin@admin.comquot;, crypted_password: quot;e66e...abdquot;, salt: quot;731f...b96quot;, created_at: quot;2008-06-29 20:21:10quot;, updated_at: quot;2008-06-29 20:21:10quot;, remember_token: nil, remember_token_expires_at: nil> SELECT * FROM quot;usuariosquot; WHERE (quot;usuariosquot;.quot;loginquot; = 'admin') LIMIT 1 >> admin.tarefas => [#<Tarefa id: 1, usuario_id: 1, duracao: 2, descricao: quot;Criando demo de Railsquot;, data_inicio: quot;2008-06-29 20:21:40quot;, created_at: quot;2008-06-29 20:21:40quot;, updated_at: quot;2008-06-29 20:21:40quot;>] SELECT * FROM quot;tarefasquot; WHERE (quot;tarefasquot;.usuario_id = 1) >> admin.anotacoes => [#<Anotacao id: 1, tarefa_id: 1, anotacao: quot;testequot;, created_at: quot;2008-06-29 21:29:52quot;, updated_at: quot;2008-06-29 21:29:52quot;>] SELECT quot;anotacoesquot;.* FROM quot;anotacoesquot; INNER JOIN tarefas ON anotacoes.tarefa_id = tarefas.id WHERE ((quot;tarefasquot;.usuario_id = 1)) Cenário Antigo # tabela 'revistas' clientes_revistas class Revista < ActiveRecord::Base # tabela 'clientes_revistas' has_and_belongs_to_many :clientes cliente_id: int end revista_id: int # tabela 'clientes' class Cliente < ActiveRecord::Base clientes # tabela 'clientes_revistas' has_and_belongs_to_many :revistas id: int end class RevistasController < ApplicationController revistas def add_cliente() end def remove_cliente() end id: int end class ClientesController < ApplicationController Qual dos dois controllers def add_revista() end def remove_revista() end está certo? end A revista controla o cliente ou o cliente controla a revista?

Cenário REST assinaturas class Assinatura < ActiveRecord::Base belongs_to :revista cliente_id: int belongs_to :cliente end revista_id: int class Revista < ActiveRecord::Base has_many :assinaturas clientes has_many :clientes, :through => :assinaturas end id: int class Cliente < ActiveRecord::Base has_many :assinaturas revistas has_many :revistas, :through => :assinaturas end id: int class AssinaturasController < ApplicationController def create() end def destroy() end end Tabelas many-to-many, normalmente podem ser um recurso próprio RESTful Rails Parte 3: ActiveResource

Active Resource • Consumidor de recursos REST • Todo scaffold Rails, por padrão, é RESTful • Para o exemplo: mantenha script/server rodando Active Resource >> require 'activesupport' => true >> require 'activeresource' => [] Via console IRB class Tarefa < ActiveResource::Base self.site = quot;http://localhost:3000quot; end >> t = Tarefa.find :first => #<Tarefa:0x1842c94 @prefix_options={}, @attributes={quot;updated_atquot;=>Sun Jun 29 20:21:40 UTC 2008, quot;idquot;=>1, quot;usuario_idquot;=>1, quot;data_inicioquot;=>Sun Jun 29 20:21:40 UTC 2008, quot;descricaoquot;=>quot;Criando demo de Railsquot;, quot;duracaoquot;=>2, quot;created_atquot;=>Sun Jun 29 20:21:40 UTC 2008} >> t = Tarefa.new(:descricao => 'Testando REST', :duracao => 1, :data_inicio => Time.now) => #<Tarefa:0x183484c @prefix_options={}, @attributes={quot;data_inicioquot;=>Sun Jun 29 19:00:57 -0300 2008, quot;descricaoquot;=>quot;Testando RESTquot;, quot;duracaoquot;=>1} >> t.save => true

Múltiplas Respostas http://localhost:3000/tarefas/1/anotacoes/1.xml Múltiplas Respostas # app/controllers/anotacoes_controller.rb class AnotacoesController < ApplicationController ... # GET /anotacoes/1 # GET /anotacoes/1.xml def show @anotacao = @tarefa.anotacoes.find(params[:id]) respond_to do |format| format.html # show.html.erb format.xml { render :xml => @anotacao } end end ... end

Testes Compilar != Testar Tipos de Teste • Testes Unitários: Models • Testes Funcionais: Controllers • Testes Integrados: Cenários de Aceitação

Fixtures • Carga de dados específicos de testes! • test/fixtures/nome_da_tabela.yml • Não se preocupar com números de primary keys • Associações podem se referenciar diretamente através do nome de cada entidade • Dar nomes significativos a cada entidade de teste restful-authentication quentin: login: quentin email: quentin@example.com salt: 356a192b7913b04c54574d18c28d46e6395428ab # SHA1('0') crypted_password: c38f11c55af4680780bba9c9f7dd9fa18898b939 # 'monkey' created_at: <%= 5.days.ago.to_s :db %> remember_token_expires_at: <%= 1.days.from_now.to_s %> remember_token: 77de68daecd823babbb58edb1c8e14d7106e83bb aaron: login: aaron email: aaron@example.com salt: da4b9237bacccdf19c0760cab7aec4a8359010b0 # SHA1('1') crypted_password: 028670d59d8eff84294668802470c8c8034c51b5 # 'monkey' created_at: <%= 1.days.ago.to_s :db %> remember_token_expires_at: remember_token: old_password_holder: login: old_password_holder email: salty_dog@example.com salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test created_at: <%= 1.days.ago.to_s :db %>

tarefas e anotações # test/fixtures/tarefas.yml # test/fixtures/anotacoes.yml aula: one: usuario: quentin tarefa: aula duracao: 1 anotacao: Aula de Rails descricao: Dando Aula data_inicio: 2008-06-28 21:33:32 two: tarefa: aula academia: anotacao: Precisa corrigir prova usuario: aaron duracao: 1 three: descricao: Exercitando tarefa: academia data_inicio: 2008-06-28 21:33:32 anotacao: Mudando rotina Ajustando - Parte 1 require File.dirname(__FILE__) + '/../test_helper' class AnotacoesControllerTest < ActionController::TestCase fixtures :tarefas, :usuarios, :anotacoes def setup @tarefa = tarefas(:aula) declarando quais end fixtures carregar def test_should_get_index get :index, :tarefa_id => @tarefa.id assert_response :success assert_not_nil assigns(:anotacoes) end def test_should_get_new adicionando get :new, :tarefa_id => @tarefa.id assert_response :success a chave :tarefa_id end ao hash params def test_should_create_anotacao assert_difference('Anotacao.count') do post :create, :tarefa_id => @tarefa.id, :anotacao => { :anotacao => quot;testequot; } end assert_redirected_to tarefa_anotacao_path(@tarefa, assigns(:anotacao)) end

Ajustando - Parte 2 def test_should_show_anotacao get :show, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id assert_response :success end def test_should_get_edit get :edit, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id assert_response :success end hash params def test_should_update_anotacao put :update, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id, :anotacao => { :anotacao => quot;testequot;} assert_redirected_to tarefa_anotacao_path(@tarefa, assigns(:anotacao)) end def test_should_destroy_anotacao assert_difference('Anotacao.count', -1) do delete :destroy, :tarefa_id => @tarefa.id, :id => anotacoes(:one).id end assert_redirected_to tarefa_anotacoes_path(@tarefa) ajustando end end rotas nomeadas Ajustando - Parte 3 # test/functional/tarefas_controller.rb require File.dirname(__FILE__) + '/../test_helper' class TarefasControllerTest < ActionController::TestCase fixtures :tarefas, :usuarios ... get :show, :id => tarefas(:aula).id ... end mudar de “one” para “aula”

#write presentations

Add a comment

Related presentations

Related pages

Impacta Show Day - Rails Test Drive | AkitaOnRails.com

Eu já palestrei sobre Ruby on Rails pela Impacta e parece que o pessoal gostou, por isso estou retornando no próximo dia 5 de julho, para mostrar R...
Read more

ActionView::Helpers::DateHelper - Ruby on Rails

Module ActionView::Helpers::DateHelper ... discard_day - Set to true if you don't want to show a ... day] in the en locale that ships with Rails).: ...
Read more

Rails Date class - Ruby on Rails API

Ruby on Rails 5.0.0 Class Date ... day_format = ActiveSupport::Inflector.ordinalize(date.day) date ... show | on GitHub ...
Read more

Abandoned Rails: Home

Abandoned Rails, a website dedicated to abandoned railroads and the railroad companies that operated them. Thousands of ...
Read more

SNL highlights: Show pokes fun at off-the-rails Trump ...

SNL highlights: Show pokes fun at off-the-rails Trump rallies. Ariana Grande hosted and preformed as a musical guest on Saturday Night Live, ...
Read more

Schedules & Maps > Commuter Rail - MBTA

Schedules & Maps Commuter Rail Middleborough/Lakeville Middleborough/Lakeville. ... For train information at Middleborough/Lakeville Station tune to 1630 AM.
Read more

Rail industry conferences, exhibitions and trade fairs

Comprehensive listing of rail industry conferences, exhibitions and trade fairs around the world. ... Show only events for . July 2016.
Read more

impacta on Twitter - Trendsmap

See where impacta is trending on Twitter ... Trendsmap shows you the latest trends ... Subscribe to Trendsmap Plus for 5 times more trends, 7 day history ...
Read more

Workshops de TI, Gestão, Design e Marketing Digital | Impacta

A Impacta Certificação e Treinamento é presença garantida nos maiores eventos das áreas de TI, Gestão e Design, além de oferecer sempre os melhores ...
Read more