Blog do Urubatan
msgbartop
Desenvolvedor, Palestrante, Escritor, Nerd Assumido e Pai do Marcus :D
msgbarbottom

24 Oct 07 Um exemplo de chat com Ruby On Rails e Juggernaut (utilizando AJAX Push)

English Version here
Antes de iniciar a implementação do chat, nos precisamos instalar as dependências do juggernaut, ele precisa das gems json e eventmachine instaladas, para instalar ambas basta executar o seguinte comando:

  • gem install -y json eventmachine

Agora estamos prontos para começar a brincar …
Primeiro, vamos criar uma aplicação rails e instalar o plugin juggernaut com os seguitnes comandos:

  • rails -d sqlite3 chattest
  • cd chattest
  • script/plugin install svn://rubyforge.org//var/svn/juggernaut/trunk/juggernaut

O Juggernaut usa um servidor externo implementando Flash XML Push, e precisamos configurar este servidor, então vamos editar o arquivo: config/juggernaut.yml
Coloquei aqui no blog apenas as configurações que precisei alterar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUSH_PORT: 8080
...
DEFAULT_CHANNELS: 
- "chat"
...
PUSH_HELPER_HOST: "localhost"
...
SECRET: "481516232342edededededed"
...
LOGIN_GET_URL: "http://localhost:3000/session/login"
LOGOUT_GET_URL: "http://localhost:3000/session/logout"
...
SESSION_ID: "_chattest_session_id"
...
BASE64: true

Precisei alterar PUSH_PORT Por que no linux apenas o root pode ouvir em portas inferiores a 1024, e mesmo assim eu não acho que a porta 443 padrão seja uma boa escolha, ja que esta é a porta padrão do protocolo HTTPS.
A linhe com PUSH_HELPER_HOST precisa ser alterada para o mesmo nome do servidor que for utilizado para a acessar a aplicação, em desenvolvimento, localhost deve resolver, mas não esqueça de alterar esta configuração quando for fazer o deploy do seu chat.
LOGIN_GET_URL e LOGOUT_GET_URL são utilizadas para notificar a aplicação quando um usuário conecta ou desconecta, apenas o desconectar é realmente importante para nós.
A SESSION_ID precisa ser igual ao nome do cookie utilizado como marcador de sessão da aplicação, no rails 1.2.x fica no application controller, no rails 2.0 esta configuração vai para o enviroment.rb
e BASE64 é a configuração mágica que habilita o uso dos helpers do rails para geração do Javascript que vai ser utilizado.

Agora vamos começar a criar o layout da aplicação.
Crie um arquivo de nome pubic/stylesheets/public.css com o seguinte conteúdo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
body {
  background-color: white;
}
#users {
  float: left;
  width: 200px;
  height: 400px;
  border-style: inset;
  overflow: auto;
  color: white;
  background-color: gray;
}
#dasd {
  height: 400px;
  margin-left: 5px;
  border-style: inset;
  overflow: auto;
  color: white;
  background-color: gray;
}
#controls {
  clear: both;
  padding: 0 0 0 0;
  height: 55px;
  vertical-align: top;
  border-style: inset;
  overflow: auto;
  color: white;
  background-color: gray;
}

e o arquivo: app/views/layouts/application.rhtml com o seguinte conteúdo.

1
2
3
4
5
6
7
8
9
10
<html>
  <head>
    <title>Chat Test</title>
    <%= stylesheet_link_tag 'public' %>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Com o layout pronto (sim, esta bem feio, mas eu não sou designer eu sou um desenvolvedor, então solicite ao designer da sua equipe a criação de um novo layout para este chat :D ) vamos criar o código fonte necessário e o banco de dados com estes 4 comandos:

  • script/generate model online_user username:string session_id:string last_seen:date online:boolean
  • script/generate controller session
  • script/generate controller chat index
  • rake db:migrate

Layout pronto, arquivos criados, agora só precisamos escrever o código da aplicação :D
Vamos começar editando o model OnlineUser (app/model/online_user.rb) para adicionar algumas validações, elas não são realmente necessárias, são resquícios da primeira tentativa de implementação do chat, mas mesmo assim mantive elas no código :D

1
2
3
4
class OnlineUser < ActiveRecord::Base
	validates_presence_of :username, :session_id, :last_seen
	validates_uniqueness_of :username, :if => Proc.new {|user| user.online }
end

Agora a view principal da aplicação no arquivo:app/views/chat/index.rhtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- Register with Juggernault -->
<%= listen_to_juggernaut_channels [:generic],session.session_id %>
<!-- The Users List -->
<div id="users">
  <ul id="users_list"></ul>
</div>
<!-- The messages pane -->
<div id="dasd"></div>
<!-- The controls pane (login and send messages) -->
<div id="controls"><%= render :partial => 'login' %></div>
<!-- An util javascript to scroll the messages window -->
<script type="text/javascript">
  function scrollMessages(){
    $('dasd').scrollTop = $('dasd').scrollHeight;
  }
</script>

Como podem ver é bem simples, apenas 3 DIVs, um javascript para fazer o scroll da tela de mensagens e um comando para inicializar o juggernaut no canal “generic” e utilizando o session_id como identificador individual do usuário.
A DIV de mensagens esta com o id dasd por que eu estava testando algumas possibilidades de conflito e esqueci de mudar o ID depois :D

Como pode ser visto na página index, precisaremos de um partial de nome “login” e vamos também precisar de um de nome “controls”, então vamos começar codificando o partial “controls”: app/views/chat/_controls.rhtml

1
2
3
4
5
6
<% form_remote_tag(
      :url => { :action => :say },
      :complete => "$('message').value = '';$('message').focus();" ) do %>
      <%= text_field_tag( 'message', '', { :size => 90, :id => 'message'} ) %>
      <%= submit_tag "Send" %>
  <% end %>

Bastante simples, possui apenas uma tag remote_form_tag que fara um post para a action “say”, um campo de texto para a mensagem e um botão de submit, quando o formulário é enviado o foco volta para o campo message para que o usuário possa digitar a próxima mensagem …

E o partial de login: app/views/chat/_login.rhtml

1
2
3
4
5
6
7
<%= "#{@message}<br/>" if @message %><% form_remote_tag(
      :url => { :action => :login },
      :complete => "$('username').value = ''",
      :after => "$('login').disabled = true" ) do %>
      <%= text_field_tag( 'username', '', { :size => 90, :id => 'username'} ) %>
      <%= submit_tag "Join", :id => 'login' %>
  <% end %>

Bastante parecido com o anterior, mas faz o post para uma action de nome “login”, e o javascript agora desabilita o botão quando o formulário é enviado, e também possui uma mensagem que deve aparecer para o usuário informando que o apelido ja esta sendo utilizado.

Agora todas as views ja estão prontas, podemos passar para a codificação da lógica da aplicação.
Como podemos ver nas views até agora, precisaremos de um controller de nome “chat” com dois métodos: login e say
vamos analisar um pouco o código do chat controller: app/controllers/chat_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class ChatController < ApplicationController
  #this method does not need to exist, but I like to see it here, it only needs to render the index.rhtml view
  def index
  end
  def login
    #creates a new OnlineUser record, this is used to store who are the users that are online now
	@user = OnlineUser.new
	@user.username = Juggernaut.html_and_string_escape params[:username]
	@user.session_id = session.session_id
	@user.online = true
	@user.last_seen = Time.now
        #if we can save, it means that there is no other user with the same nick online, so this user can join the chat
	if @user.save
          #let's save the username in the session for future reference
	  session[:username] = @user.username
          #if there are online users, fill the users box for the new user know who is online
	  @users = OnlineUser.find(:all, :conditions => ["online = true and id != ?", @user.id]) 
          if @users.size >0 
            data = render_to_string(:update) do |page|
              @users.each {|u|
                page.insert_html :bottom, :users_list, %Q{<li id="user_#{u.username}">#{u.username}</li>}
              }
	    end
            #send the javascript only to the new user
            Juggernaut.send_to(@user.session_id, data)
          end
          #create a javascript call to add the new user to the end of the online users list
          data = render_to_string(:update) do |page|
            page.insert_html :bottom, :users_list, %Q{<li id="user_#{@user.username}">#{@user.username}</li>}
            page.insert_html :bottom, :dasd, "<b>user #{@user.username} just joined the chat</b><br/>"
          end
          #add the new user to the chat channel
          Juggernaut.add_channel(@user.session_id, 'chat')
          #send the javascript to all users in the chat channel
          Juggernaut.send_data(data, 'chat')
	  render(:update) do |page|
	    page.replace_html 'controls', :partial => "controls"
          end
	else
          @message = 'This nick name is already in use, please choose another'
	  render(:update) do |page|
	    page.replace_html 'controls', :partial => "login"
	  end
	end
  end
 
  def say
    #escape the message, that way the user can not harm others sending HTML ot JavaScript commands
    message = "#{session[:username]}: #{Juggernaut.html_and_string_escape(params[:message])}"
    #create a javascript to add the new message to the end of the messages screen and scroll the div to the bottom
    data = render_to_string(:update) do |page|
      page.insert_html :bottom, :dasd, "#{message}<br/>"
      page.call "scrollMessages"
    end
    #send the message to all users
    Juggernaut.send_data(data, 'chat')
    render :nothing => true
  end
end

O método say é bastante simples, então vamos começar analisando este:
Primeiro a mensagem é reconstruída adicionando o nome do usuário no inicio da mensagem enviada removendo todos os possíveis códigos HTML ou Javascript para garantir a segurança dos outros usuários.
Então é construido um Javascript utilizando o JavaScriptBuilder do rails e o método render_to_string para adicionar a nova mensagem no final da div “dasd” e fazer o scroll caso necessário.
Então este Javascript é enviado para todos os clientes que estão ouvindo o canal “chat”.

Agora um pouco sobre o método login:

  • nas linhas 7 a 11 apenas criamos e configuramos um model OnlineUser.
  • Se o novo usuário puder ser salvo no banco de dados então não existe nenhum outro usuário online com o mesmo apelido, e podemos continuar, caso contrário mostramos uma mensagem para o usuário para que este escolha outro apelido
  • nas linhas 17 a 23 é criado um javascript para popular a DIV users do novo usuário com a lista dos usuários que ja estavam online
  • E na linha 25 este código é enviado apenas para este usuário.
  • entre a linha 28 e 31 é criado um javascript que vai adicionar o novo usuário a div users de todos os usuários e enviar uma mensagem para todos dizendo que o chat possui um novo participante.
  • na linha 33 o novo usuário é adicionado ao canal “chat”
  • na linha 35 este script é enviado para todos os usuários
  • E entre as linhas 36 e 38 é utilizado o helper de ajax padrão do rails para substituir o conteúdo da DIV controls com a partial controls que possui o formulário que permite que o usuário envie mensagens para outros integrantes do chat.

Esta é quase toda a lógica que precisamos, a única parte que falta é remover um usuário da lista de usuários online de todos os participantes quando este fechar o browser ou deixar o chat por qualquer motivo, para isto vamos ao método logout do controller session: app/controllers/session_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SessionController < ApplicationController
  #Called when a user disconnect (a refresh in the browser causes this to be called too)
  def logout
    #search for the user record using the session_id
    @u = OnlineUser.find_by_session_id(session.session_id)
    reset_session
    #if a user was found
    if @u
      username = @u.username
      #remove it from the database
      @u.destroy
      #remove from the online users list from all users, and tell others this user left the chat
      data = render_to_string(:update) do |page|
        page.remove "user_#{username}"
        page.insert_html :bottom, :dasd, "<b>User #{username} left the chat</b><br/>"
      end
      Juggernaut.send_data(data,'chat')
    end                
    render :nothing => true
  end
 
  def login
    render :nothing => true
  end
end

O método login não faz nada, pois não precisamos desta notificação para o chat, então vamos a descrição do método logout …

  • Na linah 5 procuramos o usuário no banco de dados usando o id da sessão
  • se um usuário é encontrado
  • ele é removido do banco de dados na linha 8
  • e entre as linhas 13 e 17 é criado um javascript que remove o usuário da lista de usuários online e adiciona uma mensagem avisando que o usuário deixou o chat, e no final este script é enviado para todos os usuários ainda ativos

E isto é tudo pessoal! (isto só fica engraçado em ingles :( )
Agora só falta executar a aplicação.
Para executar esta aplicação, precisamos iniciar o servidor do Rails como padrão, e então iniciar o push_server, para fazer isto basta executar os seguintes dois comandos.

  • script/server
  • script/push_server

E acessar o seu chat novo em folha no endereço: http://localhost:3000/chat
Eu criei este exemplo emquanto estudava um pouco sobre o Juggernaut, é possível que existam maneiras mais fáceis de implementar isto, mas eu acredito que seja um bom ponto de partida.

Quaisquer dicas para melhorar o exemplo são muito bem vindas.

Você gostou deste post? Compartilhe:

Tags: , , , , , , , , , ,

18 Oct 07 Recebi a camiseta do Eclipse :D

Recebi ontem a camiseta do Eclipse, por causa do review que escrevi sobre o Europa :D

Eu preferia a jaqueta, mas como o review foi publicado em portugues eu ja sabia que não ia ganhar ela mesmo :D

PS.: a camiseta polo é bem legal, com o logo do eclipse bordado :D

Você gostou deste post? Compartilhe:

Tags: ,

17 Oct 07 Integração contínua sincrona com Rails/Rake


Bom, integração contínua acho que todos concordam que é uma necessidade em qualquer projeto de software, mas a integração contínua assíncrona utilizada normalmente tem alguns problemas, como por exemplo:

  • O código mesmo não passando nos testes é comitado
  • Os testes só serão executados algum tempo depois pelo servidor de integração, e só no final disto é que o desenvolvedor ficara sabendo se ocorreu algum problema de integração com o código que ele ja não esta mais trabalhando
  • O desenvolvedor provavelmente ja esta no meio de outra tarefa quando receber o e-mail dizendo que ocorreu algum problema com a tarefa anterior

Eu não estou dizendo que não é interessante existir um servidor de integração, é interessante para que todos os envolvidos no projeto tenham sempre acesso aos relatórios e a última versão do sistema compilado, mas não acho que deva ser dele a tarefa de integrar o sistema.

Ou seja, o que antigamente era o servidor de integração, agora passa a ser apenas um servidor de builds, e gerador de relatórios sobre o código fonte e progresso do projeto.

O ideal é que o desenvolvedor, durante o processo de commit, seja obrigado a executar todos os testes, e este commit só ser realmente executado se todos os testes passarem.

Claro que isto pode ser melhorado, adicionando ainda algumas restrições, por exemplo, exigir uma cobertura de testes mínima, mas no meu caso, eu desenvolvi esta task do Rake para um projeto em que estou trabalhando sozinho, e estou utilizando o GIT para controle de versões (escreverei um post sobre o GIT ainda esta semana se der tempo), e estou utilizando integração sincrona também em alguns projetos com Java, em outra oportunidade escrevo sobre o processo com Java.

Estou adotando esta prática de integração sincrona sempre que possível.

Mas voltando ao assunto, é bastante fácil implementar isto em um projeto Rails, basta seguir estes passos:

1 – criar um arquivo de nome ‘git.rake’ dentro do diretório lib/tasks com o seguinte conteúdo:

namespace :git do
  desc "Update every thing before the tests"
  task :update do
    puts "Lets update it all, but we are using GIT so we already have the latest source for this repo"
  end
  desc "Run all tests, if all are OK, then commit every thing to the git local repository"
  task :commit => [:update, :test] do
    puts "No test failures, now we can commit it all"
    exec 'git commit -a'
  end
end

Como podem ver esta task do Rake é bem simples, e com certeza isto vai aumentar muito a qualidade do projeto!

2 – sempre ao final de uma tarefa, em vez de executar o comando “git commit -a”, executar o comando: “rake git:update”

Só isto, a partir de agora você esta utilizando integração continua sincrona :D

Claro que esta task do Rake pode ser melhorada ainda, pode ser configurado por exemplo um pull de um repositório central, ou um update de um subversion ou qualquer coisa do gênero, mas acho que ja serve como exemplo para quem quiser começar a utilizar integração sincrona.

Se todos os testes passarem, o GIT vai abrir o VIM para que o desenvolvedor edite a mensagem de commit, depois da mensagem editada o código alterado é comitado sem problemas e sem medo de quebrar o próximo build com tudo integrado.

E vocês, o qu acham deste processo de integração continua? preferem sincrona ou assincrona? por que?

Você gostou deste post? Compartilhe:

Tags: , , ,

15 Oct 07 Blog em inglês

Faz tempo que to pensando em começar a escrever em inglês, mas acabava nunca montando o tal blog.

Sexta a noite resolvi começar a usar o dominio que eu comprei a algum tempo atraz o http://www.urubatan.info.

Os primeiros posts vão ser traduções do que eu postei por aqui, mas em pouco tempo vai ter conteúdo original por la também, a maior parte dos “how to” que eu escrever vou publicar por la, mas ainda não decidi qual vai ser a real diferença dos dois blogs, a principio a idéia é só atingir uma audiencia maior, ja que neste aqui a idéia é publicar coisas em portugues para quem não fala ingles ainda :D

Bom acho que era isto, espero ter tempo para manter os dois atualizados, mas este aqui é prioridade ja que ja tem muita gente que passa por aqui todos os dias :D

Pretendo ir trabalhar fora do brasil em uns 2 ou 3 anos, então o outro blog vai servir também (se tudo der certo) para que eu fique um pouco mais conhecido fora do brasil (se eu conseguir leitores para ele :D )

Uma das diferenças é que vou publicar alguns posts sobre mobilidade com symbian também por la, o que eu publicava antes no blogspot, só não migrei os posts ainda :D

Bom era isto, este post foi só para fazer um pouquinho de propaganda do blog novo.

http://www.urubatan.info

Você gostou deste post? Compartilhe:

Tags: ,

13 Oct 07 Frase do dia – politica

Acabei de ouvir isto no filme “Homem do ano”

Politicos são como fraldas, ambos devem ser trocados frequentemente e pelo mesmo motivo! 

Acho que se o povo começar a pensar assim é possível que as coisas melhores um pouco :D

Você gostou deste post? Compartilhe:

Tags: ,

13 Oct 07 Alteração dos permalinks do blog

Acabei de alterar os permalinks do blog, antes era /ano/mes/dia/nomedopost/ agora coloquei só /nomedopost/

Isto deve gerar URLs bem menores, e se tudo der certo, os links antigos não vão parar de funcionar :D

Espero não perder muitas posições no google por causa disto, caso contrário volto  a configuração anterior :D

Estou utilizando o plugin Permalinks Migration do wordpress para fazer a mágica :D

Você gostou deste post? Compartilhe:

Tags: ,

12 Oct 07 Ruby fora dos trilhos – Nitro and Og!

O Ruby começou a ganhar espaço nas empresas e na blogosfera principalmente por causa do Rails, mas o Rails não é o único framework para desenvolvimento Web em Ruby, um destes outros frameworks é o Nitro Framework ele tem mais ou menos a mesma idade do Rails, mas tem muito menos documentação, e que eu saiba bem menos usuários também.

Neste post vou falar um pouquinho dos meus primeiros 30 minutos com o Nitro …

Uma das coisas que eu mais gosto do Rails e que não é possível de se fazer com o Nitro é simplesmente alterar uma classe com o servidor rodando e as alterações se refletirem automagicamente no browser, mas vamos deixar isto de lado por enquanto …

O nitro diferente do Rails tem mais de uma abordagem para o desenvolvimento, é possível desenvolver aplicações utilizando MVC como o Rails, mas também é possível desenvolver aplicações direto em páginas como no ASP ou no PHP.

Para começar com o nitro basta seguir este passo a passo:

  • gem install -y nitro (isto vai instalar o nitro e as suas dependências)
  • gen app rocketpower (isto vai criar uma aplicação nitro)
  • cd rocketpower
  • ruby run.rb (eu precisei editar o arquivo e adicionar um require ‘rubygems’ na segunda linha)
  • acessar o endereço http://locahost:9000

Pronto, você esta executando a sua primeira aplicação Nitro!

Até o momento estou achando o Nitro muito confuso, provavelmente por causa da falta de documentação, ou dos exemplos toscos, mas seguem algumas coisinhas legais …

Para programar orientado a páginas, basta criar uma página no diretório public com a extensão .xhtml e quando quiser escrever código Ruby basta coloca-lo entre <?r e ?> ou então quando for para simplesmente imprimir texto usar diretamente #{…} como em qualquer string Ruby.

O nitro não possui uma estrutura de diretórios padrão como o Rails, os arquivos com o código da aplicação são declarados diretamente utilizando require no run.rb, acho que por isto não existe reload automático.

O nitro não obriga a extender uma classe para criar uma classe persistente (na verdade não é o Nitro que faz isto é o Og que é o framework de persistencia utilizado pelo Nitro), ele vai persistir qualquer coisa que tiver um método “serializable_attributes” que é criado automagicamente quando se utiliza os helpers para definição de campos do Og (setting).


Ele não tem nada parecido com migrations, pelo menos não que eu tenha encontrado até agora.

O código do Nitro e do Og eu achei muito mais simples de ler e entender o que esta acontecendo do que o código do Rails, já o código das aplicações escritas com o Nitro eu achei muito bagunçado, parece a grande maior parte dos códigos PHP que se encontra por ai (nada contra PHP apenas contra péssimos programadores PHP que infelizmente são a maioria).

Uma coisa que eu achei bem legal no nitro, é que os parametros para um método de um controller são recebidos como parâmetros do método mesmo e não no mapa “params”.

Bom, o exemplo que eu queria fazer para este post vai ficar para outra hora, pois vou precisar estudar um pouco mais o Nitro e o Og para poder fazer qualquer coisa que não me deixe envergonhado, achei os exemplos bagunçados demais, muitas classes por arquivo e coisas assim …

Por enquanto o Og parece muito verde ainda, ele havia passado um bom tempo com o desenvolvimento quase parado, mas voltou a se mexer agora com o “boom” do Rails.

Ele tem algumas coisas legais, mas por enquanto fico com o Rails :D

  1. Mais sobre Ruby e Rails
  2. Site oficial do Rails
  3. Mais sobre o Nitro
  4. Tutorial sobre Rails
Você gostou deste post? Compartilhe:

Tags: ,

11 Oct 07 Alguns bits de tecnologia :D

Segue mais uma daquelas grandes coletâneas de links que falam de tudo um pouco, tem Java, .NET, Ruby, Rails, Python, …

E este último merece um certo destaque, um post sobre a péssima postura da Aptana para com a comunidade, quando eles pegaram um monte de código que foi escrito pela comunidade, juntaram com o código do Eclipse e resolveram mudar a licensa de tudo, se alguem esquentar a cabeça com isto eles poderiam até mesmo ser processados, por que estão quebrando a licensa do eclipse e por conseqüência também do Radrail, e quem baixar os fontes hoje não pode mais criar uma distribuição diferente baseado nele.

Bom, acho que era isto, vou tentar escrever com mais frequencia para não acumular tanta coisa junto :D

Você gostou deste post? Compartilhe:

Tags: , , ,

10 Oct 07 Nova feature do .NET: processar você(framework) por todas as features que você tem

Brincadeiras a parte, saiu mais um dos comerciais do pessoal do RailsEnvy.com, desta vez é o Ruby on Rails X .NET …

Os comerciais não são a coisa mais engraçada da face da terra, mas alguns deles até que são legais …
Este último que não gostei muito, este outro com .NET também foi mais legal

Você gostou deste post? Compartilhe:

Tags: ,

09 Oct 07 O pessoal de sampa esta se mexendo, mas nos aqui do RS ja vamos começar também

Ruby On Rails LogoSe você mora em SP e programa em rails, acha interessante ou quer saber do que se trata, de uma passada no blog do Akita e participe do encontro que eles estão organizando.

Mas se você mora no RS não se desanime por que nos ja estamos começando a organizar algo parecido por aqui também :D

Você gostou deste post? Compartilhe:

Tags: , ,