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, Andy Hunt e Chad Fowler; 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 orientada a objetos Não existem tipos primitivos
Linguagem de script interpretada; Muito influenciada por Smalltalk e Perl; Contém conceitos de linguagens funcionais (closures blocos de código); ( g ); Completamente dinâmica (tudo pode mudar 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 fim da linha, oras; Variáveis não tem tipo, elas simplesmente guardam um objeto qualquer, de qualquer tipo; Quando nós vamos dar nomes a variáveis, normalmente separamos nomes compostos com _ ;
Eu faço quack(), nadar() e voar(), igual a qualquer outro pato, eu sou ou não um 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 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 entre parenteses; Blocos de código, como métodos, classes, ifs são sempre fechados com, aqui não tem {} não;
Em Ruby não se usa return, se a úli última expressão no corpo de um método for avaliada para um valor, esse valor é retornado (Lembra do Effective Java? Não? Pois deveria!); O uso de parênteses não é obrigatório na chamada de métodos, mas tenha cuidado com a legibilidade; Bem, você ainda pode usar return se quiser, mas é feio, oras;
class Song def initialize(name, artist, duration) @name = name @artist = artist @duration = duration musica = Song.new Denied, Sonic Syndicate, 3 musica.inspect i
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 de classe new ; Não é possível fazer sobrecarga de construtores em Ruby =( ; Variáveis de instância tem o nome começado por @ ;
class Song attr_writer :duration attr_reader :duration attr_acessor :title song = Song.new( new("bicylops", "Fleck", 260) song.duration = 257 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 objeto; attr_acessor faz o mesmo que os dois anteriores juntos;
class Song def duration_in_minutes @duration/60.0 def duration_in_minutes=(new_duration) @duration = (new_duration * 60).to_i
Criamos métodos de acesso a um atributo, mas o cliente não sabe se são métodos ou se ele está acessando os atributos t diretamente; t Sobrescrevemos o operador = (atribuição) para a nossa propriedade, Ruby tem sobrecarga de operadores (alguns, apenas)!
class SongList @@calls_total MAX_TIME = 5*60 # 5 minutos MIN_TIME = 1*60 # 1 minuto def SongList.is_too_long(song) song.duration > MAX_TIME def self.is_too_short(song) song.duration < MIN_TIME
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 MCl MyClass def method1 # o padrão é public protected def method2 private def method3 public def method4
Os níveis de acesso são definidos em blocos, quando não há definição, é public, quando se quer definir i um nível de acesso, 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] # 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 fazer a = Array.new ; Arrays são acessados pelo índice, tanto positivo (ordem crescente) como negativo (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 ; 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 0...@songs.length return @songs[i] g[] if title == @songs[i].name return nil
class SongList def with_title(title) @songs.find { song title == song.name }
Blocos são pedaços de código que podem ser passados como parâmetros para funções, para fazer algum trabalho especial, 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 fib_up_to(1000) { f print f, " " }
O método não tem nada de especial, a sua definição fica igual a qualquer outro método e ele pode ou não receber parâmetros; Na implementação do método, você invoca o método especial yield para executar o bloco, se o bloco recebe parâmetros, você os 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}
#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 de instância;
1..1010 # [12345678910] [1,2,3,4,5,6,7,8,9,10] 'a'..'z ' ' # lembra do alfabeto? my_array = [ 1, 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) @volume = volume def to_s '#' * @volume def <=>(other) self.volume <=> other.volume def succ raise(indexerror, "Volume too big") if @volume >= 9 VU.new(@volume.succ)
num = 81 6.times do puts "#{num.class}: #{num}" num *= num
def cool_dude(arg1="miles", d " arg2="coltrane", " arg3="roach") "#{arg1}, #{arg2}, #{arg3}." cool_dude # "Miles, Coltrane, Roach." cool_dude( dude("bart") Bart) # "Bart Bart, Coltrane, Roach. " cool_dude("bart", "Elwood") # "Bart, Elwood, Roach. " cool_dude("bart", "Elwood", "Linus") # "Bart, Elwood, Linus."
def meth_three 100.times do num square = num*num return num, square if square > 1000 meth_three three # [32, 1024]
def five(a, b, c, d, e) "I was passed #{a} #{b} #{c} #{d} #{e}" five(1, 2, 3, 4, 5 ) # "I was passed 1 2 3 4 5" five(1, 2, 3, *['a' [a, 'b']) # "I was passed 1 2 3 a b" five(*(10..14).to_a) # "I was passed 10 11 12 13 14"
class SongList def create_search(name, params) #... list.create_search('short jazz songs', :genre => :jazz, :duration_less_than => 270)
puts "a = #{a}" if debug print total unless total.zero? f 2 if x == 2 puts x elsif x < 0 puts #{x} é menor que zero else puts Caiu no else
leap = case when year % 400 == 0: true when year % 100 == 0: false else year % 4 == 0
case input_line when "debug" dump_debug_infod dump_symbols when /p\s+(\w+)/ dump_variable($1) when "quit", "exit" exit else print "Illegal command: #{input_line}"
print "Hello\n" while false begin print "Goodbye\n" while false while x < 10 puts X é #{x} x = x + 1
3.times do print "Ho! 0.upto(9) do x print x, " " [ 1..5 ].each { val puts #{val} }
for i in ['fee', 'fi', 'fo', 'fum'] print i, " " for i in 1..3 print i, " "
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...
for i in 1..100 print "Now at #{i}. Restart? " retry if gets =~ /^y/i
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 de vas de produtos a clientes; Uma interfaces para clientes, outra para a p, p administração;
rails depot #sério, é só isso =P
README Rakefile Documentação Arquivo de build components/ Esqueça =P doc/ lib/ script/ Mais documentação Código genérico da aplicação Scripts de build vor/ Plugins para a sua aplicação app/ db/ log/ test/ config/ tmp/ public/ Código da sua aplicação! Migrations e geração de banco Arquivos de log Testes da sua aplicação Configuração geral Arquivos temporários do Rails 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 do t t.column :title, :string t.column :description, :text t.column :image_url, :string ti t.column :price, :decimal, :precision => 8, :scale => 2, :default => 0 def self.down drop_table :products
O seu banco de dados d com controle de versão! Define tabelas, adiciona linhas, cria índices e ainda faz a janta! Contém métodos utilitários para criar, alterar e remover tabelas, colunas e linhas; Você faz no up e desfaz no down, não se esqueça da ordem!
:null indica se pode ser null ou não (truefalse) :limit indica o tamanho máximo do campo :default indica o valor padrão do campo :precision indica a precisão do número :scale 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
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) def self.down drop_table :line_items
ruby script/generate scaffold product admin #não, você não vai usar isso até o fim
def index list rer :action => 'list' def list @product_pages, @products = paginate :products, :per_page => 10 def show @product = Product.find(params[:id]) def new @product = Product.new
def create @product = Product.new(params[:product]) if @product.save flash[:notice] = 'Product was successfully created.' redirect_to to :action => 'list' else rer :action => 'new' def edit @product = Product.find(params[:id])
def update @product = Product.find(params[:id]) if @product.update_attributes( update attributes( params[:product]) flash[:notice] = 'Product was successfully updated.' redirect_to :action => 'show', :id => @product else rer :action => 'edit'
rake db:migrate #rodando o migration ruby script/server webrick #iniciando o servidor http://localhost:3000/admin #voilá!
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 nome da action que está executando; headers hash com os cabeçalhos HTTP (você pode colocar novos se quiser); params parametros que vieram da requisição, podem ser acessados usando símbolos ou strings; flash objeto que pode guardar uma mensagem entre uma requisição e outra (como enviar uma mensagem pra um redirect);
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 use o atributo cookies para enviar cookies para o navegador, nunca envie usando o atributo headers ;
Representa uma sessão HTTP de um usuário com a aplicação; Você pode colocar valores dentro dela, como se ela fosse um hash; Os 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 %> <%= rer :partial => 'form' %> <%= submit_tag 'Editar' %> <% %> <%= link_to 'Mostrar', :action => 'show', :id => @product %> <%= link_to 'Voltar', :action => 'list' %>
form_tag é uma função de ajuda que gera um formulário através de um bloco de código; rer :partial => form, vai incluir o arquivo _form dentro da página submit_tag Editar gera um botão clicável link_to Editar :action => show gera um link para o método show do controller atual;
<%= error_messages_for 'product' %> <!--[form:product]--> <p><label for="product_name">nome</label><br/> <%= text_field t :product, :name %></p> <p><label for="product_category">categoria</label><br/> <%= select :product, :category_id, @categories, :prompt => "Escolha uma categoria"%> </p> <p><label for= "product_label label" >Marca</label><br/> br/ <%= 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]-->
text_field e select são utilitários para gerar um campo de entrada e um select HTML, existem outros utilitários i para todos os outros componentes HTML; O uso de símbolos, como em text_field :product, :name é para dizer de onde vem o 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 segundo item é o value do select;
<% form_for f :product, :url => { :action => :create } 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> <% %>
É 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 qual o objeto real; 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 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
Td Toda a validação é definida id dentro do próprio 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 _ 'product' %>
ruby script/generate migration add_test_data data class AddTestData < ActiveRecord::Migration def self.up Product.create( :title => 'Pragmatic Version Control', :description => %{<p>with Subversion</p>}, :image _ url => '/images/svn.jpg', :price => 28.50) def self.down Product.delete_all
<%= link_to Remover', { :action => 'destroy', y, :id => product }, :confirm => Tem certeza?", :method => :post %>
module ApplicationHelper def format_price( price ) number_to_currency( price, :precision => 2, :unit => 'R$ ', :separator => ',', :delimiter => '.' )
Cada controller tem o seu próprio helper e existe um helper padrão que sempre está disponível para todos os controllers; Todo o trabalho que possa ser de lógica das views (como formatação, geração de templates t e etc) deve ser feito por eles; Ó NAS VIEWS NÃO PODE HAVER LÓGICA, enteu? Ou quer que desenhe?
<% form_for(:picture, f it :url => {:action => 'save'}, :html => { :multipart => true }) do form %> Comment: <%= form.text_field("comment" ) %><br/> Upload your picture: <%= form.file_field("uploaded_picture" p ) %><br/> <%= submit_tag("upload file" ) %> <% %>
class Picture < ActiveRecord::Base validates_format_of :content_type, :with => /^image/, :message => "-- you can only upload pictures it def uploaded_picture=(picture_field) self.name = base_part_of(picture_field.original_filename) f l f l self.content_type = picture_field.content_type.chomp self.data = picture_field.read def base_part_of(file_name) File.basename(file_name).gsub(/[^\w._-]/, '' )
def picture @picture = Picture.find(params[:id]) s_data(@picture.data, :filename => @picture.name, :type => @picture.content_type, :disposition => "inline" )
Ei, você já está mexo 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 que você definiu lá no q 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 for inválido, save! lança uma exeção se não conseguir salvar ou se o objeto for inválido; destroy remove um objeto do banco 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 das propriedades d do objeto e tentam t enviar uma atualização para o banco de dados; create cria um novo objeto com o hashh de 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 pode ter N itens); Métodos adicionados: product(reload = false) product= build_product( hash ) create_product( hash )
Define o lado 1 do relacionamento um para muitos; Adiciona os métodos (e ainda outros...): orders(force_reload=false) reload=false) orders <<order orders.push(order1,...) orders.replace(order1,...) orders.delete(order1,...) orders.destroy_all orders.size orders.empty?
class Article < ActiveRecord::Base has_and_belongs_to_many :users class User < ActiveRecord::Base has_ and_ belongs _ to_ many :articles
Account.transaction do account1.deposit(100) account2.withdraw(100)
name = params[:name] pos = Order.find(:all, :conditions => "name = '#{name}' and pay_type yp = 'po'" )
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 => ["name = :name and pay_type = :pay_type 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 primeiro item a ser trazido; :first no lugar de :all, para trazer apenas um item;
items = LineItem.find_by_sql("select *, " + " quantity*unit_price as total_price, " + " products.title as title " + " from line_items, products " + " where line_items.product_id = products.id " ) li = items[0] puts "#{li.title}: #{li.quantity}x#{li.unit_price} => #{li.total_price} 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/