Apostila de Ruby do Maurício Linhares
Transcrição
Apostila de Ruby do Maurício Linhares
Maurício Linhares Maurício Linhares Desenvolvedor Java Ruby C# JUGLeader do PBJUG Completamente, absurdamente, absolutamente, plagiadamente, baseado em..... Programming Ruby – The Pragmatic Programmers Guide 2nd. Por Dave Thomas, A d Hunt H t e Chad Ch d Fowler; F l Andy Agile Web Development with Rails 2nd. Dave Thomas, David Heinemeier Hanson; Do Japão Yukihiko “Matz” Matsumoto Trazida ao mundo ocidental pelos Pragmatic Programmers Andy Hunt e Dave Thomas (livro da machadinha) Completamente p orientada a objetos j Não existem tipos primitivos Linguagem Muito “de script” interpretada; influenciada por Smalltalk e Perl; Contém conceitos de linguagens funcionais ((closures – blocos de código); g ); Completamente dinâmica (tudo pode mudar em tempo de execução); IRB é o “Interactive Ruby”, o console da linguagem; Pode-se enviar comandos ou se definir classes, módulos, funções e qualquer outra coisa pra se utilizar durante uma sessão; numero = 30 outro_numero = -20 numero + outro_numero numero + outro_numero.abs Não se usam ‘;’ ou qualquer outra coisa pra indicar o fim de uma linha, o fim da linha é o fi da fim d linha, li h oras; Variáveis não tem tipo, elas simplesmente guardam um objeto qualquer, de qualquer ti tipo; Quando nós vamos dar nomes a variáveis, normalmente separamos nomes compostos com “_”; “ ” Eu faço “quack()”, “nadar()” nadar() e “voar()”, voar() , igual a qualquer outro pato,, eu sou ou não um p pato? Pois é, você ê perdeu a aula de Smalltalk né? Não importa qual o tipo do objeto, o que importa são as mensagens (métodos?) que ele é capaz de responder; Se faz quack, nada e voa feito um pato, então pra mim é um pato; def soma( primeiro, segundo) primeiro + segundo end soma( 10, 20) soma 50, 90 A definição de um método começa com “def” depois vem o nome do método e depois seus parâmetros â t entre t parenteses; t Blocos de código, como métodos, classes, “ifs” são sempre fechados com “end”, aqui ã ttem “{}” não; ã não Em E Ruby R b não ã se usa “ “return”, ” se a úl última i expressão no corpo de um método for avaliada para um valor, valor esse valor é retornado (Lembra do “Effective Java”? Não? Pois deveria!); ) O uso de p parênteses não é obrigatório na g chamada de métodos, mas tenha cuidado com a legibilidade; Bem, você ainda pode usar “return” se quiser mas é feio quiser, feio, oras; class l S Song def initialize(name, artist, duration) @name = name @artist = artist @duration d = duration d end end musica = Song.new “Denied”, “Sonic Syndicate”, 3 musica.inspect i i t A definição de uma classe começa com a palavra “class”; As classes são sempre “abertas”, você pode redefinir os métodos de uma classe em qualquer lugar, é só declarar ela outra vez; Nomes de classe normalmente são definidos usando CamelCase (como em NomeDeClasse); Os contrutores são os métodos “initialize()”, que são invocados indiretamente, através do método ét d d de classe l “ “new”; ” Não é possível fazer sobrecarga de construtores em Ruby =( ; Variáveis por “@”; de instância tem o nome começado class Song attr_writer :duration attr_reader :duration _ :title attr_acessor end Song new("Bicylops" song = Song.new( Bicylops , "Fleck" Fleck , 260) song.duration = 257 song title = ‘Triciclops’ song.title Triciclops attr_reader define métodos de acesso a um atributo de instância de um objeto; attr_writer define métodos de alteraçao a um atributo de instância de um objeto, assim como o reader, ele cria a variável dentro do bj t objeto; attr_acessor faz o mesmo que os dois anteriores juntos; class Song def duration_in_minutes @duration/60.0 end def duration_in_minutes=(new_duration) (new duration * 60).to_i 60) to i @duration = (new_duration end end Criamos métodos de acesso a um atributo, mas o cliente não sabe se são métodos ou se ele l está tá acessando d os atributos t ib t di diretamente; t t Sobrescrevemos o operador “=“ (atribuição) para a nossa propriedade, Ruby tem b d operadores d ((alguns, l )! sobrecarga de apenas)! class l S SongList Li @@calls_total MAX TIME = 5*60 # 5 minutos MAX_TIME i MIN_TIME = 1*60 # 1 minuto d f SongList.is_too_long(song) def S l song.duration > MAX_TIME end def self.is_too_short(song) song.duration < MIN_TIME end end Variáveis de classe são definidas com dois “@”, como em “@@variavel_de_classe”; Constantes são definidas com o seu nome completo em “caixa alta” (na verdade é só a primeira letra, mas use o NOME COMPLETO); Métodos de classe são definidos colocando o nome da classe antes do nome do método ou usando “self” (é o “this” em Ruby); class l MyClass M Cl def method1 # o padrão é public end protected def method2 end private d f method3 th d3 def end public def method4 end end Os níveis de acesso são definidos em blocos, quando não há definição, é public, quando se quer d definir fi i um nível í ld de acesso, d deve-se utilizar um bloco de acesso; Um bloco termina assim que outro se iniciar; a = [ 3.14159, "pie", 99 ] a[0] # 3.14159 a[3] # nil a << 123 # adicionando um item a[4] = 456 #adicionando outro item a[-1] a[ 1] # 456 – acessando de trás pra frente a[2..4] # [99, 123, 456] Arrays são definidos pela simples declaração de “[]”, como em “a = []”, também pode-se f fazer “a “ = Array.new”; A ” Arrays são acessados pelo índice, tanto positivo (ordem crescente) como negativo ( d d t ) (ordem decrescente); Quando se acessa um índice que não existe em um array, ele retorna “nil”; Novos items podem ser colocados no array simplesmente utilizando o próprio operador “[]” como em “ “[]”, “a[10] [10] = 10” 10”; Para se adicionar um item no fim do array usa-se o operador “<<“, como em “a << 10”; Arrays podem ser pegos em ‘pedaços’ como em “a[1..3]”, que cria um novo array com os itens do índice 1 ao 3; h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' } h.length # 3 h['dog'] # "canine" h['cow'] = 'bovine' h[12] [ ] = 'dodecine' h['cat'] = 99 São conjuntos de “chave-valor” (Lembra do Map em Java?); São declarados com o uso de “{}” como em “h = {}”, ou usando Hash.new; Para se adicionar itens basta usar o operador [], como em “h[‘chave’] = ‘valor’”; class SongList def with_title(title) for i in [email protected] g [ ] if title == return @songs[i] @songs[i].name end return nil end end class SongList def with_title(title) @songs.find {|song| title == song.name } end end Blocos são pedaços de código que podem ser passados como parâmetros para funções, para fazer f algum l ttrabalho b lh especial, i l como filtragem, ordenação e outros; Você pode definir os seus próprios métodos it bl que aceitam blocos; def fib_up_to(max) i1, i2 = 1, 1 # atribuição paralela (i1 = 1 and i2 = 1) while i1 <= max yield i1 i1,, i2 = i2,, i1+i2 end end fib up to(1000) {|f| print f, fib_up_to(1000) f ""} O método não tem nada de especial, a sua definição fica igual a qualquer outro método e ele l pode d ou não ã receber b parâmetros; â t Na implementação do método, você invoca o método especial “yield” para executar o bl b parâmetros, â t ê os bloco, se o bl bloco recebe você passa para o método “yield”; 'escape using "\\"' # escape using "\" 'That\'s right' ! #That's right "Seconds/day: #{24*60*60}" # Seconds/day: 86400 def to_s Song: #@song Artist: #{@artist} #{@artist}” “Song: end #Q!String de muitas linhas com aspas duplas e acessando variáveis #{variavel}! #q( String de muitas linhas com aspas simples, aqui não pode ter variável ) ‘a’ << ‘b’ << ‘c’ # ‘abc’ Strings são sequências ê de caracteres de 8 bits, não necessariamente Unicode; Elas podem ser definidas usando ‘’ (aspas simples) ou “” (aspas duplas); Usando aspas duplas você pode colocar expressões ou variáveis com o uso do caracter “#” seguido de “{}” para variáveis de método ou funções ou apenas ‘@’ para variáveis á d de instância; â 1 10 # [1 1..10 [1,2,3,4,5,6,7,8,9,10] 2 3 4 5 6 7 8 9 10] ' ' ' ‘ # lembra 'a'..'z‘ l b d do alfabeto? lf b ? my_array = [ 1, 2, 2 3] 0...my_array.length # [0,1,2,3,4] (1..10).to_a # [1, 2, 3, 4, 5, 6, 7, 8, 9,10] ('bar'..'bat').to_a # ["bar", "bas", "bat"] São um conjunto de objetos em sequência, ê normalmente caracteres ou números; São definidos através da declaração de [ ‘valor inicial’ .. ‘valor final’ ], como em [1..100]; Você pode definir os seus próprios ranges com o operador “<=>” (disco voador?); class VU include Comparable attr :volume def initialize(volume) @ l @volume = volume l end def to_s '#' * @volume l end def <=>(other) self.volume <=> other.volume end def succ raise(IndexError, "Volume too big") if @volume >= 9 VU.new(@volume.succ) end end num = 81 6.times do puts "#{num.class}: #{num}" num *= num end deff cool_dude(arg1="Miles", d l d d ( 1 "Mil " arg2="Coltrane", 2 "C l " arg3="Roach") #{arg1}, #{arg2} #{arg2}, #{arg3} #{arg3}." "#{arg1} end cool_dude # "Miles, Coltrane, Roach." cool dude("Bart") Coltrane Roach cool_dude( Bart ) # "Bart Bart, Coltrane, Roach." cool_dude("Bart", "Elwood") # "Bart, Elwood, Roach " Roach. cool_dude("Bart", "Elwood", "Linus") # "Bart, Elwood, Linus. Linus." def meth_three 100.times do |num| square = num*num q if square q > 1000 return num,, square end end meth three # [32 meth_three [32, 1024] def five(a, b, c, d, e) "I was passed #{a} #{b} #{c} #{d} #{e}" end five(1, 2, 3, 4, 5 ) # "I was passed 1 2 3 4 5" five(1 2 b" five(1, 2, 3 3, *['a' [ a , 'b']) b ]) # "II was passed 1 2 3 a b five(*(10..14).to_a) # "I was passed 10 11 12 13 14 14" class SongList def create_search(name, params) # ... end end list.create_search('short jazz songs', :genre => :jazz, :jazz :duration_less_than => 270) puts "a " = #{ #{a}" }" if debug d b print i totall unless l total.zero? l ? iff x == 2 puts x elsif x < 0 puts “#{x} é menor que zero” else puts ‘Caiu no else’ end leap = case when year % 400 == 0: true when year % 100 == 0: false else yyear % 4 == 0 end case input_line i li when "debug" d dump_debug_info d b i f dump_symbols when h /p\s+(\w+)/ dump_variable($1) when "quit", "exit" exit else print "Illegal command: #{input_line}" end print "Hello\n" while false begin print "Goodbye\n" p y end while false while x < 10 puts “X X é #{x} #{x}” x=x+1 end d 3.times do print "Ho! “ end 0.upto(9) do |x| x "" print x, end [ 1..5 ].each {|val| puts “ #{val} ” } for i in ['fee', 'fi', 'fo', 'fum'] print i, " " end for i in 1..3 i "" print i, end while line = gets next if line =~ /^\s*#/ # pular comentários break if line =~ /^END/ # parar no fim # executar coisas redo if line.gsub!(/`(.*?)`/) { eval($1) } # process line ... end for i in 1..100 print "Now at #{i}. Restart? " retry if gets =~ /^y/i end Agora você programa em Ruby! Admita, você já estava cansado de tanto mexer e não ver nada né? Um framework web completo, baseado no MVC “web” (lembra disso?); Ele define aonde cada pedaço de código deve ficar e como ele deve ser acessado; Convenções são melhor do que a liberdade de configuração; Né pra repetir código ó não, rapá! Projeto “Depot” do livro “Agile Web Development With Ruby”; Aplicação Uma de vendas de produtos a clientes; interfaces p para clientes,, outra p para a administração; rails depot #sério, é só isso =P README Documentação Rakefile Arquivo de build components/ Esqueça =P doc/ Mais documentação lib/ Código genérico da aplicação script/ Scripts de build vendor/ d / Pl i para a sua aplicação Plugins li ã app/ Código da sua aplicação! db/ Migrations e geração de banco log/ Arquivos de log test/ Testes da sua aplicação config/ Configuração geral tmp/ Arquivos temporários do Rails public/ Arquivos estáticos (html, js, css) development: adapter: mysql database: depot_development username: root password: host: localhost rake db:migrate ruby script/generate model product class CreateProducts < ActiveRecord::Migration def self.up create_table _ :products p do | |t| | t.column :title, :string t.column :description, :text t l t.column :image_url, i g l :string ti g t.column :price, :decimal, :precision => 8, :scale => 2, :default => 0 end end def self.down self down drop_table :products end end O seu b banco d de d dados d com controle l d de versão! Define tabelas, adiciona linhas, cria índices e ainda faz a janta! Contém criar alterar métodos utilitários para criar, e remover tabelas, colunas e linhas; Você faz no “up” e desfaz no “down”, não se esqueça q ç da ordem! :null ll – indica i di se pode d ser null ll ou não ã ((truefalse) :limit – indica o tamanho máximo do campo :default – indica o valor padrão do campo :precision :scale – indica a precisão do número – indica a escala do número (quantas casas depois da vírgula) create_table :authors_books, :id => false do |t| t.column :author_id, :integer, :null => false t.column :book_id, :integer, :null => false end class CreateLineItems < ActiveRecord::Migration def self.up execute "alter table line_items add constraint fk_line_item_products foreign key (product_id) references products(id)“ end def self.down drop_table :line_items end end ruby script/generate scaffold product admin #não,, você não vai usar isso até o fim def index list render :action => 'list' end def list @ d t @product_pages, g @ d t = paginate @products gi t :products, d t :per_page => 10 end def show @product = Product.find(params[:id]) end def new @product = Product.new end def create @product = Product.new(params[:product]) if @product.save p flash[:notice] = 'Product was successfully created.' redirect to :action => 'list' redirect_to list else render :action => 'new' end end def edit @product = Product.find(params[:id]) end deff update d d t @product = Product.find(params[:id]) if @product.update_attributes( @product update attributes( params[:product]) flash[:notice] = 'Product Product was successfully updated.' redirect_to :action => 'show' , :id => @product else render d :action ti => 'edit' ' dit' end end rake db:migrate #rodando o migration ruby script/server webrick #iniciando o servidor http://localhost:3000/admin #voilá! p Todo mundo vem de ApplicationController; As URLs nomalmente são .../’nomeDoController’/’metodoDoController ’/’aqui pode vir um id ou não’, como em “/admin/edit/10”; Todos os métodos públicos de um controller podem se chamados, métodos não públicos não ficam disponíveis (segurança rapá!); action_name ti – nome da d action ti que está tá executando; headers – hash com os cabeçalhos HTTP (você pode colocar novos se q p quiser); ); params – parametros que vieram da requisição, í podem ser acessados usando símbolos ou strings; flash fl h – objeto bj t que pode d guardar d uma mensagem entre uma requisição e outra (como enviar uma g p pra um redirect); ); mensagem domain – retorna o domínio que veio na requisição remote_ip – retorna o ip que gerou a requisição; env – hash de parametros enviados pelo browser (como língua); method – retorna o método http invocado como um símbolo, :get, :post e ect; get?, post?, put?, delete?, xml_http_request?, xhr? É um hash de strings, onde você ê pode passar qualquer valor; Você só pode passar Strings; Sempre p use o atributo “cookies” p para enviar cookies para o navegador, nunca envie usando o atributo “headers”; Representa R uma sessão ã HTTP d de um usuário ái com a aplicação; Você pode colocar valores dentro dela, como se ela fosse um hash; Os coisa valores podem ser qualquer coisa, contanto que seja um objeto que possa ser serializado;; EVITE colocar muita coisa na sessão;; rake db:sessions:create rake db:migrate config.action_controller.session_store = :active_record_store _ _ <% form_tag :action => 'update', :id => @product do %> <%= render :partial => 'form' %> <%= submit_tag 'Editar' %> <% end %> <%= link_to 'Mostrar', :action => 'show', :id => @product %> | @p <%= link_to 'Voltar', :action => 'list' %> form_tag f é uma ffunção ã d de ajuda j d que gera um formulário através de um bloco de código; render :partial => ‘form’ form , vai “incluir” incluir o arquivo “_form” dentro da página submit_tag link_to ‘Editar’ gera um botão clicável ‘Editar’ :action => ‘show’ gera um link p para o método ‘show’ do controller atual; <%= error_messages_for <% error messages for 'product' product %> <!--[form:product]--> <p><label for="product_name">Nome</label><br/> <%= % text_field t t fi ld :product, d t :name %></p> % / <p><label for="product_category">Categoria</label><br/> <%= select l :product, d :category_id, d @categories, :prompt => "Escolha lh uma categoria"%> </p> p label for= for "prod ct label">Marca</label><br/> Marca /label br/ <p><label product_label <%= select :product, :label_id, @labels, :prompt => "Escolha uma marca" %> </p> <p><label for="product_price">Preço real</label><br/> <%= text_field :product, :real_price %></p> <! [eoform:product] > <!--[eoform:product]--> text_field e select são utilitários para gerar um campo de entrada e um select HTML, existem i t outros t utilitários tilitá i para ttodos d os outros componentes HTML; O uso de símbolos, como em “text_field d t :name” ” é para di d vem o :product, dizer d de onde campo e que campo é que nós queremos mostrar (veja o código fonte gerado!); @categories = Category.find( :all, :order => :name).map { |c| [c.name, c.id] } Ao mostrar alguma coisa em um select, ele deve vir em um array de arrays, onde o primeiro item é o “label” do select e o d item it l ”d l t segundo éo“ “value” do select; <% % form_for f f :product, d t :urll => { :action ti => :create t }d do |form| %> <p>Title: <%= form.text_field :title, :size => 30 %></p> <p>Description: <%= form.text_area :description, :rows => 3 %></p> <p>Price: <%= form.text_field :price, :size => 10 %></p> <p><%= submit_tag %></p> <% % end d %> % É feito especificamente para gerar formulários para objetos do modelo; Os métodos utilitários de criação de componentes são chamados direto no “form” e não é necessário repetir diversas vezes bj t real; l quall o objeto De resto é igual a form_tag; class Product < ActiveRecord::Base validates_presence_of :title, :description, :image_url validates_numericality_of :price validates_uniqueness_of :title validates format of :image_url, validates_format_of :image url, :with => %r{\.(gif|jpg|png)$}i, :message => “deve ser um gif, jpg ou png" protected def validate errors.add(:price, “deve ser ao menos 0.01" ) if price.nil? || price < 0.01 end end Toda T d a validação lid ã é definida d fi id d dentro d do próprio ó i objeto do modelo; Existem diversos métodos utilitários para diversos tipos de validação; Se nenhum dos métodos resolve o seu problema, implemente o método “validate”; As mensagens aparecem em -> <%= error_messages_for _ g _ 'product' p %> ruby script/generate migration add_test_data add test data class AddTestData < ActiveRecord::Migration g def self.up Product.create( :title titl => 'P 'Pragmatic g ti V Version i C Control' t l' , :description => %{<p>With Subversion</p>}, :image g _url => '/images/svn.jpg' g jpg , :price => 28.50) end d f self.down def lf d Product.delete_all end end <%= link_to ‘Remover', { :action => 'destroy', y , :id => p product }, :confirm => “Tem certeza?" , :method => > :post %> module ApplicationHelper def format_price( price ) number_to_currency( price, :precision => 2, :unit => 'R$ ', :separator => ',', :delimiter => '.' ) end end Cada controller tem o seu próprio helper e existe um helper padrão que sempre está di disponível í l para ttodos d os controllers; t ll Todo o trabalho que possa ser de lógica das views (como formatação, geração de t l t e etc) t ) deve d f it por eles; l templates ser feito NAS VIEWS NÃO PODE HAVER LÓGICA, Ó entendeu? Ou quer que desenhe? <% % fform_for(:picture, f ( i t :url => {:action => 'save'}, :html => > { :multipart => > true }) do |form| %> Comment: <%= form.text_field("comment" ) %><br/> Upload your picture: <%= form.file_field("uploaded_picture" ( p p ) %><br/> <%= submit_tag("Upload file" ) %> <% end %> class Picture < ActiveRecord::Base validates_format_of :content_type, :with => /^image/, :message => ""-- you can only l upload l d pictures“ i t “ def uploaded_picture=(picture_field) self.name lf = base_part_of(picture_field.original_filename) b f f ld l fl self.content_type = picture_field.content_type.chomp self.data = picture_field.read end def base_part_of(file_name) File.basename(file_name).gsub(/[^\w._-]/, '' ) end end def picture @picture = Picture.find(params[:id]) send_data(@picture.data, p , :filename => @picture.name, :type => @picture.content_type, :disposition => > "inline" inline ) end Ei, você já está mexendo com ele desde o primeiro migration! É a abstração de banco de dados de Rails; Baseado no padrão de projeto “active record”; Cada coluna q que você definiu lá no migration, vira uma propriedade do objeto; ‘save’ e ‘save!’, salva ou atualiza um objeto, ‘save’ retorna true se conseguir salvar, false senão ã ou o objeto bj t for f inválido, i álid ‘‘save!’ !’ lança l uma exeção se não conseguir salvar ou se o objeto for inválido; ‘d t ’ ‘destroy’ bj t d remove um objeto do b banco d de dados e não deixa que nenhum dos seus atributos seja alterado; update_attributes e update_attributes!, recebem um hash com os parâmetros e valores l das d propriedades i d d d do objeto bj t e ttentam t enviar uma atualização para o banco de dados; ‘ t ’ ‘create’ i um novo objeto bj t com o hash h h de d cria parametros passado e salva ele no banco de dados automaticamente; Define um relacionamento de 1 pra N (como em um item pertence a um produto, mas um produto d t pode d tter N it itens); ) Métodos adicionados: product(reload = false) product= build_product( hash ) create_product( hash ) Define D fi o llado d 1d do relacionamento l i um para muitos; Adiciona os métodos (e ainda outros...): orders(force_reload=false) orders(force reload=false) orders <<order orders.push(order1, ...) orders.replace(order1, ...) orders.delete(order1, ...) orders.destroy_all orders.size orders.empty? d ? class Article < ActiveRecord::Base has_and_belongs_to_many :users end class User < ActiveRecord::Base g _to_manyy :articles has_and_belongs end Account.transaction do p ( ) account1.deposit(100) account2.withdraw(100) end name = params[:name] pos = ( , Order.find(:all, :conditions => "name = '#{name}' and pay_type p y_ yp = 'po'" p ) name = params[:name] pos = Order.find(:all, :conditions => ["name = ? and pay_type = 'po'" , name]) name = params[:name] pay_type = params[:pay_type] pos = ( , Order.find(:all, :conditions => pay type = :pay_type :pay type" , [["name name = :name and pay_type {:pay_type => pay_type, :name => name}]) :order – para ordenar a consulta, use símbolos; :limit – quantidade máxima de itens que deve ser trazida; :offset :first – primeiro item a ser trazido; – no lugar de :all, para trazer apenas um item; u te ; items = LineItem.find_by_sql("select *, " + " quantity*unit_price as total_price, " + " products.title as title " + _ ,p products " + " from line_items, " where line_items.product_id = products.id " ) li = items[0] puts "#{li.title}: #{li.quantity}x#{li.unit_price} => > #{li.total_price} #{li total price}" average = Order.average(:amount) max = Order.maximum(:amount) min = Order.minimum(:amount) ( ) total = Order.sum(:amount) number = Order.count #tudo isso pode receber :conditions, como o find order = Order.find_by_name("Dave Thomas" ) orders = Order.find_all_by_name("Dave Thomas" ) order = Order.find_all_by_email(params['email' _ _ y_ (p [ ]) Obrigado pessoal! \o/