Impacta - Show Day de Rails
Upcoming SlideShare
Loading in...5
×
 

Impacta - Show Day de Rails

on

  • 5,101 views

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

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

Statistics

Views

Total Views
5,101
Views on SlideShare
5,101
Embed Views
0

Actions

Likes
1
Downloads
117
Comments
2

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

12 of 2

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • Shoow. =)
    Are you sure you want to
    Your message goes here
    Processing…
  • very good
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Impacta - Show Day de Rails Impacta - Show Day de Rails Document Transcript

    • 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” conforme foi modificado em tarefas.yml
    • Executando $ rake test (in /Users/akitaonrails/rails/sandbox/impacta/tarefas) /Users/akitaonrails/rails/sandbox/impacta/tarefas/vendor/plugins/brazilian-rails/tasks /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/ rake_test_loader.rbquot; quot;test/unit/anotacao_test.rbquot; quot;test/unit/tarefa_test.rbquot; quot;test/unit/ usuario_test.rbquot; Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader Started ............... Finished in 0.370074 seconds. 15 tests, 28 assertions, 0 failures, 0 errors /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/ rake_test_loader.rbquot; quot;test/functional/admin/usuarios_controller_test.rbquot; quot;test/functional/ anotacoes_controller_test.rbquot; quot;test/functional/sessoes_controller_test.rbquot; quot;test/functional/ tarefas_controller_test.rbquot; quot;test/functional/usuarios_controller_test.rbquot; Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader Started ............................. Finished in 0.832019 seconds. 29 tests, 53 assertions, 0 failures, 0 errors /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.1/lib/rake/ rake_test_loader.rbquot; Estatísticas $ rake stats (in /Users/akitaonrails/rails/sandbox/impacta/tarefas) +----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 280 | 192 | 6 | 21 | 3 | 7 | | Helpers | 104 | 44 | 0 | 4 | 0 | 9 | | Models | 54 | 30 | 3 | 1 | 0 | 28 | | Libraries | 198 | 96 | 0 | 21 | 0 | 2 | | Integration tests | 0 | 0 | 0 | 0 | 0 | 0 | | Functional tests | 260 | 204 | 7 | 37 | 5 | 3 | | Unit tests | 119 | 98 | 3 | 16 | 5 | 4 | +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 1015 | 664 | 19 | 100 | 5 | 4 | +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 362 Test LOC: 302 Code to Test Ratio: 1:0.8 Esta taxa é muito baixa. Busque 1:3.0 pelo menos!
    • Assertions http://topfunky.com/clients/rails/ruby_and_rails_assertions.pdf Assertions
    • Assertions Views
    • Ajustando Layouts <!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>Admin <%= 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> Ajustando Tarefas <!-- index.html.erb --> ... <td><%=h tarefa.usuario.login if tarefa.usuario %></td> <td><%=h tarefa.duracao %></td> <td><%=h tarefa.descricao %></td> <td><%=h tarefa.data_inicio.to_s(:short) %></td> ... <!-- new.html.erb --> <h1>New tarefa</h1> <% form_for(@tarefa) do |f| %> <%= f.error_messages %> <%= render :partial => f, :locals => { :submit_text => 'Create' } %> <% end %> <%= link_to 'Back', tarefas_path %> <!-- edit.html.erb --> <h1>Editing tarefa</h1> <% form_for(@tarefa) do |f| %> <%= f.error_messages %> <%= render :partial => f, :locals => { :submit_text => 'Update' } %> <% end %> <%= link_to 'Show', @tarefa %> | <%= link_to 'Back', tarefas_path %>
    • Ajustando Tarefas <!-- _form.html.erb --> <p> <%= form.label :usuario %><br /> <%= form.collection_select :usuario_id, Usuario.all, 'id', 'login' %> </p> <p> <%= form.label :duracao %><br /> <!-- show.html.erb --> <%= form.text_field :duracao %> <p> </p> <b>Usuario:</b> <p> <%=h @tarefa.usuario.login %> <%= form.label :descricao %><br /> </p> <%= form.text_field :descricao %> ... </p> <p> <%= form.label :data_inicio %><br /> <%= form.datetime_select :data_inicio %> </p> <p> <%= form.submit submit_text %> </p> calendar_helper >> ./script/plugin install http://calendardateselect.googlecode.com/svn/tags/ calendar_date_select <!-- app/views/layouts/application.html.erb --> <%= stylesheet_link_tag 'scaffold' %> <%= stylesheet_link_tag 'calendar_date_select/default' %> <%= javascript_include_tag :defaults %> <%= javascript_include_tag 'calendar_date_select/calendar_date_select' %> <!-- app/views/tarefas/_form.html.erb --> ... <p> <%= form.label :data_inicio %><br /> <%= form.calendar_date_select :data_inicio %> </p> Sempre que instalar um plugin: reiniciar o servidor
    • calendar_helper Ajax <!-- app/views/tarefas/index.html.erb --> <h1>Listing tarefas</h1> <table id='tarefas_table'> <tr> <th>Usuario</th> <th>Duracao</th> <th>Descricao</th> <th>Data inicio</th> </tr> <% for tarefa in @tarefas %> <%= render :partial => 'tarefa_row', :locals => { :tarefa => tarefa } %> <% end %> </table> <br /> <h1>Nova Tarefa</h1> <% remote_form_for(Tarefa.new) do |f| %> <%= render :partial => f, :locals => { :submit_text => 'Create' } %> <% end %>
    • Ajax <!-- app/views/tarefas/_tarefa_row.html.erb --> <tr id=quot;<%= dom_id(tarefa) %>quot;> <td><%=h tarefa.usuario.login if tarefa.usuario %></td> <td><%=h tarefa.duracao %></td> <td><%=h tarefa.descricao %></td> <td><%=h tarefa.data_inicio.to_s(:short) %></td> <td><%= link_to 'Show', tarefa %></td> <td><%= link_to 'Edit', edit_tarefa_path(tarefa) %></td> <td><%= link_to_remote 'Destroy', :url => tarefa_path(tarefa), :confirm => 'Are you sure?', :method => :delete %></td> </tr> # app/views/tarefas/create.js.rjs page.insert_html :bottom, 'tarefas_table', :partial => 'tarefa_row', :locals => { :tarefa => @tarefa } page.visual_effect :highlight, dom_id(@tarefa) # app/views/tarefas/destroy.js.rjs page.visual_effect :drop_out, dom_id(@tarefa) # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController ... def create @tarefa = Tarefa.new(params[:tarefa]) respond_to do |format| if @tarefa.save flash[:notice] = 'Tarefa was successfully created.' format.html { redirect_to(@tarefa) } format.js # create.js.rjs format.xml { render :xml => @tarefa, :status => :created, :location => @tarefa } else format.html { render :action => quot;newquot; } format.xml { render :xml => @tarefa.errors, :status => :unprocessable_entity } end end end def destroy @tarefa = Tarefa.find(params[:id]) @tarefa.destroy respond_to do |format| format.html { redirect_to(tarefas_url) } format.js # destroy.js.rjs format.xml { head :ok } end end end
    • RJS - nos bastidores <%= link_to_remote 'Destroy', :url => tarefa_path(tarefa), :confirm => 'Are you sure?', :method => :delete %> <a href=quot;#quot; onclick=quot;if (confirm('Are you sure?')) { new Ajax.Request('/tarefas/10', {asynchronous:true, evalScripts:true, method:'delete', parameters:'authenticity_token=' + encodeURIComponent('966...364')}); }; return false;quot;>Destroy</a> <% remote_form_for(Tarefa.new) do |f| %> ... <% end %> <form action=quot;/tarefasquot; class=quot;new_tarefaquot; id=quot;new_tarefaquot; method=quot;postquot; onsubmit=quot;new Ajax.Request('/tarefas', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;quot;> ... </form>
    • FormHelper f.check_box :accepted, { :class => 'eula_check' }, quot;yesquot;, quot;noquot; f.file_field :file, :class => 'file_input' f.hidden_field :token f.label :title f.password_field :pin, :size => 20 f.radio_button :category, 'rails' f.text_area :obs, :cols => 20, :rows => 40 f.text_field :name, :size => 20, :class => 'code_input' f.select :person_id, Person.all.map { |p| [ p.name, p.id ] }, { :include_blank => true } f.collection_select :author_id, Author.all, :id, :name, { :prompt => true } f.country_select :country f.time_zone_select :time_zone, TimeZone.us_zones, :default => quot;Pacific Time (US & Canada)quot; JavaScriptGenerator page[:element] page << quot;alert('teste')quot; page.alert(quot;testequot;) page.assign 'record_count', 33 page.call 'alert', 'My message!' page.delay(20) do page.visual_effect :fade, 'notice' end page.redirect_to(:controller => 'account', :action => 'signup') page.show 'person_6', 'person_13', 'person_223' page.hide 'person_29', 'person_9', 'person_0' page.toggle 'person_14', 'person_12', 'person_23' page.insert_html :after, 'list', '<li>Last item</li>' page.remove 'person_23', 'person_9', 'person_2' page.replace 'person-45', :partial => 'person', :object => @person page.replace_html 'person-45', :partial => 'person', :object => @person page.select('p.welcome b').first.hide page.visual_effect :highlight, 'person_12'
    • PrototypeHelper <%= link_to_remote quot;Destroyquot;, :url => person_url(:id => person), :method => :delete %> <%= observe_field :suggest, :url => search_tarefas_path, :frequency => 0.25, :update => :suggest, :with => 'q' %> <%= periodically_call_remote(:url => { :action => 'invoice', :id => customer.id }, :update => { :success => quot;invoicequot;, :failure => quot;errorquot; } %> <% remote_form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => quot;edit_postquot;, :id => quot;edit_post_45quot; } do |f| %> ... <% end %> <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, :update => { :success => quot;succeedquot;, :failure => quot;failquot; } %> <%= draggable_element(quot;my_imagequot;, :revert => true) %> <%= drop_receiving_element(quot;my_cartquot;, :url => { :controller => quot;cartquot;, :action => quot;addquot; }) %> <%= sortable_element(quot;my_listquot;, :url => { :action => quot;orderquot; }) %> Entendendo Forms
    • Nome e prefixo do form <% form_for(@tarefa) do |f| %> <%= f.error_messages %> <p> <%= form.label :usuario %><br /> <%= form.collection_select :usuario_id, Usuario.all, 'id', 'login' %> </p> <p> <%= form.label :duracao %><br /> <%= form.text_field :duracao %> </p> <p> <%= form.label :descricao %><br /> <%= form.text_field :descricao %> </p> <p> <%= form.label :data_inicio %><br /> <%= form.calendar_date_select :data_inicio %> </p> <p> <%= form.submit 'Create' %> </p> <% end %> <form action=quot;/tarefasquot; class=quot;new_tarefaquot; id=quot;new_tarefaquot; method=quot;postquot;> <div style=quot;margin:0;padding:0quot;> <input name=quot;authenticity_tokenquot; type=quot;hiddenquot; value=quot;966974fa19db6efaf0b3f456c2823a7f46181364quot; /> </div> <p> <label for=quot;tarefa_usuarioquot;>Usuario</label><br /> <select id=quot;tarefa_usuario_idquot; name=quot;tarefa[usuario_id]quot;> <option value=quot;1quot;>admin</option> <option value=quot;2quot;>akita</option> </select> </p> <p> <label for=quot;tarefa_duracaoquot;>Duracao</label><br /> <input id=quot;tarefa_duracaoquot; name=quot;tarefa[duracao]quot; size=quot;30quot; type=quot;textquot; /> </p> <p> <label for=quot;tarefa_descricaoquot;>Descricao</label><br /> <input id=quot;tarefa_descricaoquot; name=quot;tarefa[descricao]quot; size=quot;30quot; type=quot;textquot; /> </p> <p> <label for=quot;tarefa_data_inicioquot;>Data inicio</label><br /> <input id=quot;tarefa_data_inicioquot; name=quot;tarefa[data_inicio]quot; size=quot;30quot; type=quot;textquot; /> <img alt=quot;Calendarquot; onclick=quot;new CalendarDateSelect( $(this).previous(), {time:true, year_range:10} );quot; src=quot;/images/calendar_date_select/calendar.gif?1214788838quot; style=quot;border:0px; cursor:pointer;quot; /> </p> <p> <input id=quot;tarefa_submitquot; name=quot;commitquot; type=quot;submitquot; value=quot;Createquot; /> </p> </form>
    • HTTP Post Processing TarefasController#create (for 127.0.0.1 at 2008-06-30 00:01:11) [POST] Session ID: BAh...c25 Parameters: {quot;commitquot;=>quot;Createquot;, quot;authenticity_tokenquot;=>quot;966974fa19db6efaf0b3f456c2823a7f46181364quot;, quot;actionquot;=>quot;createquot;, quot;controllerquot;=>quot;tarefasquot;, quot;tarefaquot;=>{quot;usuario_idquot;=>quot;1quot;, quot;data_inicioquot;=>quot;June 28, 2008 12:00 AMquot;, quot;descricaoquot;=>quot;Teste de Criaçãoquot;, quot;duracaoquot;=>quot;1quot;}} Tarefa Create (0.000453) INSERT INTO quot;tarefasquot; (quot;updated_atquot;, quot;usuario_idquot;, quot;data_inicioquot;, quot;descricaoquot;, quot;duracaoquot;, quot;created_atquot;) VALUES('2008-06-30 03:01:11', 1, '2008-06-28 00:00:00', 'Teste de Criação', 1, '2008-06-30 03:01:11') Redirected to http://localhost:3000/tarefas/12 Completed in 0.01710 (58 reqs/sec) | DB: 0.00045 (2%) | 302 Found [http://localhost/tarefas] Valores do pacote HTTP serão desserializados no hash ‘params’ Note também que :action e :controller foram extraídos de POST /tarefas Params # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController def create # pegando o hash params, na chave :tarefa @tarefa = Tarefa.new(params[:tarefa]) ... end end # equivalente a: @tarefa = Tarefa.new { :duracao=>quot;1quot;, :usuario_id=>quot;1quot;, :data_inicio=>quot;June 28, 2008 12:00 AMquot;, :descricao=>quot;Teste de Criaçãoquot; } # o hash params completo vem assim: params = {:action=>quot;createquot;, :authenticity_token=>quot;966...364quot;, :controller=>quot;tarefasquot;, :commit=>quot;Createquot;, :tarefa => {quot;usuario_idquot;=>quot;1quot;, quot;duracaoquot;=>quot;1quot;, quot;data_inicioquot;=>quot;June 28, 2008 12:00 AMquot;, quot;descricaoquot;=>quot;Teste de Criaçãoquot; } }
    • Action Mailer Envio simples de e-mail >> ./script/plugin install git://github.com/caritos/action_mailer_tls.git >> ./script/generate mailer TarefaMailer importante # config/environments/development.rb ... Para enviar config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { via Gmail :address => quot;smtp.gmail.comquot;, :port => 587, :authentication => :plain, :user_name => quot;fabioakitaquot;, :password => quot;----------quot; } config.action_mailer.perform_deliveries = true # app/controllers/tarefas_controller.rb class TarefasController < ApplicationController ... Ativa o def create @tarefa = Tarefa.new(params[:tarefa]) envio respond_to do |format| if @tarefa.save TarefaMailer.deliver_importante(@tarefa) if @tarefa.descricao =~ /^!/ ... end
    • # app/models/tarefa_mailer.rb class TarefaMailer < ActionMailer::Base def importante(tarefa, sent_at = Time.now) recipients tarefa.usuario.email subject 'Tarefa Importante' from 'fabioakita@gmail.com' sent_on sent_at body :tarefa => tarefa end # se tiver ActiveScaffold instalado def self.uses_active_scaffold? false Template ERB end end <!-- app/views/tarefa_mailer/importante.erb --> Notificação de Tarefa Importante Descrição: <%= @tarefa.descricao %> Duração: <%= @tarefa.duracao %> hs Início: <%= @tarefa.data_inicio.to_s(:short) %> # test/unit/tarefa_mailer_test.rb require 'test_helper' class TarefaMailerTest < ActionMailer::TestCase tests TarefaMailer fixtures :usuarios, :tarefas def test_importante @expected.subject = 'Tarefa Importante' @expected.from = 'fabioakita@gmail.com' @expected.to = tarefas(:aula).usuario.email @expected.body = read_fixture('importante') @expected.date = Time.now assert_equal @expected.encoded, TarefaMailer.create_importante(tarefas(:aula)).encoded end end # test/fixtures/tarefa_mailer/importante Notificação de Tarefa Importante Descrição: Dando Aula Duração: 1 hs Início: 28 Jun 21:33
    • Observações • Evitar enviar e-mails nas actions: é muito lento! • Estratégia: • Fazer a action gravar numa tabela que serve de “fila” com o status de “pendente” • Ter um script externo que de tempos em tempos puxa os pendentes, envia os e-mails e remarca como ‘enviado’ • Exemplo: usar ./script/runner para isso aliado a um cronjob. O Runner roda um script Ruby dentro do ambiente Rails, com acesso a tudo.
    • Outras Dicas Rotas $ rm public/index.html # config/routes.rb Redefine a raíz # adicionar: da aplicação map.root :tarefas # remover: Apps REST não map.connect ':controller/:action/:id' precisam disso map.connect ':controller/:action/:id.:format'
    • Time Zone • No Rails 2.1, por padrão, todo horário é gravado em formato UTC • Time.zone recebe um String, como definido em TimeZone.all • ActiveRecord converte os time zones de forma transparente Time Zone >> Time.now # horário atual, note zona GMT -03:00 => Mon Jun 30 13:04:09 -0300 2008 >> tarefa = Tarefa.first # pegando a primeira tarefa do banco => #<Tarefa id: 1 ...> >> anotacao = tarefa.anotacoes.create(:anotacao => quot;Teste com horaquot;) # criando anotacao => #<Anotacao id: 4 ...> >> anotacao.reload # recarregando anotacao do banco, apenas para garantir => #<Anotacao id: 4 ...> >> anotacao.created_at # data gravada no banco => Seg, 30 Jun 2008 16:04:31 UTC 00:00 # config/environment.rb config.time_zone = 'UTC'
    • Time Zone # config/environment.rb config.time_zone = 'Brasilia' >> Time.now # horario atual, local em GMT -3 => Mon Jun 30 13:07:42 -0300 2008 >> tarefa = Tarefa.first # novamente pega uma tarefa => #<Tarefa id: 1, ...> >> anotacao = tarefa.anotacoes.create(:anotacao => quot;Outro teste com horaquot;) # cria anotacao => #<Anotacao id: 5, tarefa_id: 1, ...> >> anotacao.created_at # horario local, automaticamente convertido de acordo com config.time_zone => Seg, 30 Jun 2008 13:08:00 ART -03:00 >> anotacao.created_at_before_type_cast # horario em UTC, direto do banco de dados => Mon Jun 30 16:08:00 UTC 2008 Time Zone Rake Tasks $ rake -D time rake time:zones:all Displays names of all time zones recognized by the Rails TimeZone class, grouped by offset. Results can be filtered with optional OFFSET parameter, e.g., OFFSET=-6 rake time:zones:local Displays names of time zones recognized by the Rails TimeZone class with the same offset as the system local time rake time:zones:us Displays names of US time zones recognized by the Rails TimeZone class, grouped by offset. Results can be filtered with optional OFFSET parameter, e.g., OFFSET=-6
    • Time Zone Rake Tasks $ rake time:zones:us * UTC -10:00 * Hawaii $ rake time:zones:local * UTC -09:00 * * UTC -03:00 * Alaska Brasilia * UTC -08:00 * Buenos Aires Pacific Time (US & Canada) Georgetown * UTC -07:00 * Greenland Arizona Mountain Time (US & Canada) * UTC -06:00 * Central Time (US & Canada) * UTC -05:00 * Eastern Time (US & Canada) Indiana (East) Adicionando Time Zone $ ./script/generate migration AdicionaTimeZoneUsuario # db/migrate/20080630182836_adiciona_time_zone_usuario.rb class AdicionaTimeZoneUsuario < ActiveRecord::Migration def self.up add_column :usuarios, :time_zone, :string, :null => false, :default => 'Brasilia' end def self.down remove_column :usuarios, :time_zone end end $ rake db:migrate <!-- app/views/usuarios/new.html.erb --> <p><label for=quot;time_zonequot;>Time Zone</label><br/> <%= f.time_zone_select :time_zone, TimeZone.all %></p>
    • Modificando ActiveScaffold # lib/active_scaffold_extentions.rb module ActiveScaffold module Helpers # Helpers that assist with the rendering of a Form Column module FormColumns def active_scaffold_input_time_zone(column, options) time_zone_select :record, column.name, TimeZone.all end end end end # config/environment.rb ... require 'active_scaffold_extentions' # app/controllers/admin/usuarios_controller.rb class Admin::UsuariosController < ApplicationController before_filter :login_required active_scaffold :usuario do |config| config.columns = [:login, :email, :time_zone, :created_at, :password, :password_confirmation ] config.columns[:time_zone].form_ui = [:time_zone] config.list.columns.exclude [ :password, :password_confirmation ] Views config.create.columns.exclude [ :created_at] config.update.columns.exclude [ :login, :created_at] end end
    • Time Zone Views Carregando Zonas # app/controllers/application.rb class ApplicationController < ActionController::Base helper :all # include all helpers, all the time include AuthenticatedSystem protect_from_forgery Retirar include before_filter :load_time_zone AuthenticatedSystem de private usuarios e sessoes def load_time_zone Time.zone = current_usuario.time_zone if logged_in? end end # app/controllers/admin/usuarios_controller.rb class Admin::UsuariosController < ApplicationController before_filter :login_required ... end
    • Carregando Zonas Segurança • No Rails 2, sessions são gravadas no cookie, mas não criptografadas. • Best Practice: não grave nada importante na session • Todo formulário HTML é protegido contra Cross Site Request Forgery (CSRF) • Toda operação ActiveRecord é sanitizada para evitar SQL Injection • Best Practice: não crie SQL manualmente concatenando strings originadas em formulários
    • Segurança # config/environment.rb config.action_controller.session = { :session_key => '_tarefas_session', :secret => 'aaa02677597285ff58fcdb2eafaf5a82a1c10572334acb5297ec94a0f6b1cf48fbcb8f54 6c00c08769a6f99695dd967a2d3fea33d6217548fcc4fd64e783caa6' } # app/controllers/application.rb class ApplicationController < ActionController::Base ... protect_from_forgery ... end <form action=quot;/tarefasquot; class=quot;new_tarefaquot; id=quot;new_tarefaquot; method=quot;postquot;> <div style=quot;margin:0;padding:0quot;> <input name=quot;authenticity_tokenquot; type=quot;hiddenquot; value=quot;5a2176fd77601a497a9d7ae8184d06b60df0ae28quot; /> </div> ... </form> JRuby
    • O que é? • Criado por Thomas Enebo e Charles Nutter • Suportado pela Sun • Compilador e Interpretador compatível com Ruby MRI 1.8.6 • Capaz de gerar bytecode Java a partir de código Ruby • Roda sobre JDK 1.4 até 1.6 Vantagens • Performance muitas vezes maior do que Ruby MRI atual • Capacidade de tirar proveito do HotSpot para otimização em runtime • Utilização de threads-nativas • Suporte a Unicode compatível com Java • Capaz de utilizar qualquer biblioteca Java
    • Desvantagens • Tempo de inicialização um pouco mais lento • Não é capaz de usar extensões feitas em C • Não é compatível com todas as gems e plugins disponíveis JRuby on Rails • Warble: capaz de encapsular uma aplicação Rails em um arquivo WAR comum • ActiveRecord-JDBC utiliza os drivers JDBC normais de Java • jetty_rails: desenvolvimento ágil mesmo em ambiente Java • Capaz de compartilhar objetos HTTPSession com aplicações Java no mesmo container
    • Instalação • Instalar o JDK mais recente (de preferência 1.6) • Baixar http://dist.codehaus.org/jruby/jruby- bin-1.1.2.zip • Descompactar e colocar o diretório bin/ no seu PATH (depende do seu sistema operacional) Instalação • jruby -v • ruby 1.8.6 (2008-05-28 rev 6586) [x86_64-jruby1.1.2] • jruby -S gem install rails • jruby -S gem install jruby-openssl • jruby -S gem install activerecord-jdbc-adapter • jruby -S gem install activerecord-jdbcmysql-adapter • jruby -S gem install activerecord-jdbcsqlite3-adapter • jruby -S gem install jdbc-mysql • jruby -S gem install jdbc-sqlite3 • jruby -S gem install jetty-rails
    • Configuração • Alterar database.yml <% jdbc = defined?(JRUBY_VERSION) ? 'jdbc' : '' %> development: adapter: <%= jdbc %>mysql database: tarefas username: root password: root • jruby -S jetty-rails Configuração • jruby -S gem install warbler • cd tarefas • jruby -S warble config # config/warble.rb Warbler::Config.new do |config| config.dirs = %w(app config lib log vendor tmp) config.gems = [quot;activerecord-jdbc-adapterquot;, quot;jruby-opensslquot;, quot;activerecord-jdbcmysql-adapterquot;, quot;jdbc-mysqlquot;] config.gem_dependencies = true config.webxml.rails.env = 'production' end
    • Configuração # config/initializers/jruby.rb if defined?(JRUBY_VERSION) && defined?($servlet_context) # Logger expects an object that responds to #write and #close device = Object.new def device.write(message) $servlet_context.log(message) end def device.close; end # Make these accessible to wire in the log device class << RAILS_DEFAULT_LOGGER public :instance_variable_get, :instance_variable_set end old_device = RAILS_DEFAULT_LOGGER.instance_variable_get quot;@logquot; old_device.close rescue nil RAILS_DEFAULT_LOGGER.instance_variable_set quot;@logquot;, device end # config/environment.rb config.action_controller.session_store = :java_servlet_store if defined?(JRUBY_VERSION) # config/database.yml production: adapter: jdbcmysql database: tarefas username: root password: root •jruby -S warble config
    • Michael Hartl Insoshi Download
    • Iniciando $ sudo gem install rails $ sudo gem install ferret $ sudo gem install sqlite3-ruby $ sudo gem install mysql $ sudo gem install image_science $ rake install $ rake db:test:prepare $ rake spec $ rake db:sample_data:reload $ script/server Admin: email: admin@example.com password: admin
    • Testes $ rake spec (in /Users/akitaonrails/rails/sandbox/insoshi) ... Finished in 9.798022 seconds 342 examples, 0 failures $ rake stats (in /Users/akitaonrails/rails/sandbox/insoshi) +----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 1436 | 1125 | 18 | 131 | 7 | 6 | | Helpers | 455 | 355 | 0 | 36 | 0 | 7 | | Models | 1307 | 766 | 18 | 103 | 5 | 5 | | Libraries | 1351 | 772 | 9 | 119 | 13 | 4 | | Model specs | 1470 | 1173 | 0 | 6 | 0 | 193 | | View specs | 73 | 56 | 0 | 0 | 0 | 0 | | Controller specs | 1199 | 1001 | 0 | 5 | 0 | 198 | | Helper specs | 143 | 100 | 0 | 0 | 0 | 0 | +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 7434 | 5348 | 45 | 400 | 8 | 11 | +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 3018 Test LOC: 2330 Code to Test Ratio: 1:0.8 Plugins NÃO USE FERRET! Prefira Sphinx Uploader Suporte a RSpec Melhor Paginador
    • Exemplo: Will Paginate <!-- app/views/forums/show.html.erb --> # app/controllers/forums_controller.rb <h2>Discussion topics</h2> def show @forum = Forum.find(params[:id]) <ol class=quot;list forum fullquot;> @topics = @forum.topics.paginate( <%= render :partial => @topics %> :page => params[:page]) </ol> end <%= will_paginate(@topics) %> Analisando $./script/plugin install svn://rubyforge.org//var/svn/visualizemodels/visualize_models $rake visualize_models (in /Users/akitaonrails/rails/sandbox/insoshi) Looking at table: schema_info Looking at table: people Looking at table: photos Looking at table: communications Looking at table: connections Looking at table: forums Looking at table: topics Looking at table: posts Looking at table: blogs Looking at table: comments Looking at table: activities Looking at table: feeds Looking at table: preferences Looking at table: email_verifications Looking at table: page_views Generated /Users/akitaonrails/rails/sandbox/insoshi/doc/model_overview_neato_hier.png Generated /Users/akitaonrails/rails/sandbox/insoshi/doc/model_overview_neato_plain.png
    • Deploying $gem install passenger $passenger-install-apache2-module # /etc/apache2/httpd.conf ... LoadModule passenger_module /opt/local/lib/ruby/gems/1.8/gems/passenger-2.0.1/ext/apache2/ mod_passenger.so PassengerRoot /opt/local/lib/ruby/gems/1.8/gems/passenger-2.0.1 PassengerRuby /opt/local/bin/ruby <VirtualHost *:80> ServerName insoshi.local DocumentRoot quot;/Users/akitaonrails/rails/sandbox/insoshi/publicquot; RailsEnv development RailsAllowModRewrite off <directory quot;/Users/akitaonrails/rails/sandbox/insoshi/publicquot;> Order allow,deny Allow from all </directory> </VirtualHost>
    • Deploying Apache 2 + Passenger App App App Rails Rails Rails Ruby Ruby Ruby MySQL
    • Phusion Evoluindo
    • Websites • akitaonrails.com • rubylearning.com • nomedojogo.com • peepcode.com • rubyonda.com • railscasts.com • rubyonbr.org • headius.blogspot.com • groups.google.com/ • nubyonrails.topfunky.com group/rails-br • rubyinside.com • planetrubyonrails.com • rubycorner.com • weblog.rubyonrails.org Livros • “Desenvolvendo a Web Ágil com Rails” - Artmed - Dave Thomas • Beginning Ruby - Peter Cooper • The Rails Way - Obie Fernandez • The Ruby Way - Hal Fulton • Advanced Rails Recipes - Mike Clark • Deploying Rails Applications - Ezra
    • Obrigado! www.akitaonrails.com