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:
Agora estamos prontos para começar a brincar …
Primeiro, vamos criar uma aplicação rails e instalar o plugin juggernaut com os seguitnes comandos:
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
) vamos criar o código fonte necessário e o banco de dados com estes 4 comandos:
Layout pronto, arquivos criados, agora só precisamos escrever o código da aplicação ![]()
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
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
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:
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 …
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.
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.
Tags: ajax, chat, example, flash, howto, push, rails, reverse, Ruby, simple, xml
Recebi ontem a camiseta do Eclipse, por causa do review que escrevi sobre o Europa
Eu preferia a jaqueta, mas como o review foi publicado em portugues eu ja sabia que não ia ganhar ela mesmo
PS.: a camiseta polo é bem legal, com o logo do eclipse bordado
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:
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
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?
Tags: Java, produtividade, Ruby, Trabalho
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
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
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
)
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
Bom era isto, este post foi só para fazer um pouquinho de propaganda do blog novo.
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
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
Espero não perder muitas posições no google por causa disto, caso contrário volto a configuração anterior
Estou utilizando o plugin Permalinks Migration do wordpress para fazer a mágica
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:
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
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
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 …
Se 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