Versão em portugues aqui
Before we start with the chat, you need to have the requisites installed, juggernaut need the gems json and eventmachine installed, so, run the following command before reading the rest of this example …
Ok, now we are ready to go!
First of all, create a rails application, and install the Juggernaut plugin with this commands:
Juggernaut uses an external Flash XML Push Server to do the reverse ajax magic, and we’ll need to configure that server, so, let’s edit the file: config/juggernaut.yml
I’ve put in the snipet bellow just the lines I changed in the file
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 |
I had to change the PUSH_PORT because I’m using a linux box and the application does not run as root, so I was not able to use the default 443 port, and I do not think that 443 is a good port choice because it is the default HTTPS port.
Make sure you change the PUSH_HELPER_HOST to the same host name as the one you are using to access the application, localhost will do the job in the development environment, but remember to change it when you publish your site in a production environment.
the LOGIN_GET_URL and LOGOUT_GET_URL are used to notify the application about clients arriving and leaving, we will really use only the leaving notification.
the SESSION_ID must be the same as the defined cookie name for your application, it is defined in the application controller for rails 1.2.x and will be moved to environment.rb for rails 2.0
and BASE64 must be set to true if we want to use the rails helpers to generate the javascript for us.
Now let’s start the layout for the application. Create a file named pubic/stylesheets/public.css with the following content:
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; } |
and a file named: app/views/layouts/application.rhtml with the following content.
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> |
in this file it is important to add the stylesheet and the default javascript includes.
With the layout ready (ok, I know it is pretty ugly, but I’m a developer not a webdesigner so, for production, ask a designer in your team for a new layout
) let’s generate the needed files and database tables with the following four commands.
Every thing ready, we just need to edit some files …
Open the OnlineUser model (app/model/online_user.rb) and change the content to something like the following
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 |
It is just a few validations, not really needed, this was my first idea for the chat, I’ve changed it a little but still works.
Now let’s code the main view of the application in the file: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> |
That is all, just tree DIVs, the tag to initialize juggernaut and a simple javascript to scroll the messages DIV to the last sent message.
the messages DIV is named dasd because I was testing some conflicts and forgot to change it back
As seen in the page above, we need a login partial, and we’ll need a controls partial too, so let’s code the controls partial (app/views/chat/_controls.rhtml) with the following code:
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 %> |
It has only a remote form tag and two fields, after the form is submited the message field is cleared and the focus os placed back in that field so the user can type another message.
And the login partial (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 %> |
Very similar to the controls partial, but it shows a message to the user if the chosen nick name is already taken.
the views are all set, and now we need the application logic, as seen in the views, we need a chat controller with two methods: login and say
Let’s take a look at the 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 |
The method say is really simple, so we’ll start explaining it:
It first build a new message appending the escaped original message to the user name, then it creates the javascript to add the message to the bottom of the messages div using the Rails JavaScriptBuilder and the render_to_string method, that returns a string instead of rendering the code directly to the client.
then it sends the message to all users subscribed to the “chat” channel, and all the users will execute that javascript.
Now a little about the login method:
This is almost all logic needed for this chat application, the only missing thing is removing the nick of users that are no more online from the users DIV from the other users, and we’ll do that in the session controller (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 |
The login method does nothing, but we have some code in the logout method …
That is all folks, we just need to run the application
to run this application we need to start the rails application server as usual, and then start the push server.
to do this, just run the following two commands:
and access your newly build chat with the URL: http://localhost:3000/chat and play a little around.
I’ve built this example while studding the juggernaut lib, so it is possible that there is a easy way to do this, but I think this is a good start point ![]()
The start idea was not to use a database, but I could not find anything like the ServletContext in java for rails (an application context), I’ll try to use ENV to store the online user names, but I could not make it work until now.
Any tips for improving this example will be very welcome.
If you enjoyed this post, make sure you subscribe to my RSS feed!
Tags: ajax, chat, example, flash, howto, push, rails, reverse, ruby, simple, xml
[...] 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: [...]
[...] here for [...]
[...] more here This entry was posted on Wednesday, October 24th, 2007 at 5:00 am and is filed under reference [...]
[...] um excelente artigo do Urubatan onde ele mostra de maneira clara como montar um chat usando o Rails, e o Juggernaut, [...]
[...] full story here [...]
[...] the details here This entry was posted on Wednesday, October 24th, 2007 at 5:00 am and is filed under cool [...]
[...] Read the rest of this great post here [...]
[...] Read the rest of this great post here [...]
I’m getting errors trying this.
#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])
Should that be online = ‘t’ ?
also now I get some javascript error. Missing “)”.
Max,
the line @users = OnlineUser.find(:all, :conditions => [”online = true and id != ?”, @user.id]) is correct, at least using MySQL as a database.
and about the javascript error, you just need to edit the juggernaut.yml and set the BASE64 option to true (it solved the error for me).
PS.: sorry for the last answer in portuguese
Urubatan,
Please post the files in a zip file. I tried your suggestions, but when I try to login, it doesn’t seem to do anything.
Thanks,
Max
[...] RSS Feed wrote an interesting post today onHere’s a quick excerptclass 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 … [...]
Nice tutorial, thanks, this looks very interesting. I followed the instructions exactly, however when I try to login, script/push_server just returns:
Connection established
Starting to parse line buffer
nope, didn’t dispatch request
Connection closed
Starting to parse request
Line buffer: [{”broadcast”:1, “channels”:”chat” …
Broadcasting dHj5…………
No such channel: chat
@Mike, change:
to
in /chat/index.rhtml.
listen_to_juggernaut_channels [:generic],session.session_id
=>
listen_to_juggernaut_channels [:chat],session.session_id
Mike, I had a similar problem, and it solved reinstaling the flash player, do not know why yet.
After the login the users list was populated? try using two browser windows to test it.
if it is not populated, try passing the :width and :height to the juggernaut listener, and using firebug to monitor the javascript calls.
I’ll try to upload the complete application as a zip file to the blog tomorow.
Matthijs, the :generic is correct, the idea is add the chat channel to the user connection only after the login.
Hi, listen_to_juggernaut_channels dont exist, in last version i have done this:
['chat'],:client_id=>session.session_id) %>
but dont works that:
@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{#{u.username}}
}
end
#send the javascript only to the new user
Juggernaut.send_to(@user.session_id, data)
Dont send to client list users. Anyone know why? Ty
Great tutorial.
One thing I don’t get is I even don’t have one juggernaut.yml under my config/ directory, only a file named juggernaut_hosts.yml, do I need to create my own by juggernaut -c juggernaut.yml, but seems it didn’t have the attribute you mentioned in the tutorial, would you please help? Thank you.
#gem list | grep juggernaut
juggernaut (0.5.4)
#gem list | grep eventmachine
eventmachine (0.12.0)
#gem list | grep json
json (1.1.2)
Hello,
I used this tutorial to create chat application with juggernaut in rails. But I faced many problems. This is might be because of the new code of the plugin juggernaut. Because when I installed plugin, i got juggernaut_hosts.yml file in the config folder, instead of juggernaut.yml.
Then I got error on the line
in view, which i changed to simply,
, and it worked.
Then I got error on methods like,
add_channel, send_to etc. which were not there in the /lib/juggernaut.rb file.
So I request you to kindly update your example with the new code in the plugin. So that it will be helpful to others also.
Thanks a lot,
Shalmali
Thanks for the feedback, I’ll update the tutorial with the newer versions of Rails and Juggernaut.
I’ll be very thankfull if you tell us about the Tutorial-update and where we can find it. thanks
i did not get any newer version of tutorial
how can i implement a private chat using juggernaut ?
I mean ,I am able to list all the online users.when I click on any users I need an chat window and should be able to chat only with that particular user.please help me.
thanks
hi,
i want to know if user closes the windows directly without logout then how we should handle the users which are online
I think the better option is a timeout …
You can use window.onclose but even this can not work if the browser window freezes
getting error chat/index listen_to_juggernaut_channels [:generic],session.session_id
Nice tutorial, definitely helped me in the right direction. However, I’m not convinced a whole new ActiveRecord model is required for online users. I’ve just implemented something similar, but instead stored the last_request and last_url on the user model.
Nice tutorial, but I cannot download the juggernaut from svn… connection refused.