Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use ActionController::Live along with Resque + Redis (for Chat application)

I am trying to build a chat feature for my rails application. I am using ActionController::Live, Puma, Resque, Redis for this. So basically in this case, redis subscribe method is running in background using resque. So far what i have done is whenever a user enters a text in below form field i.e. chat box

    <%= form_tag chat_box_publish_path, method: :get do %>
        <%= text_field_tag :query, params[:query], class: "form-control", id: "chatSearchBox",
            placeholder: 'Search' %>
    <% end %>

..the request is coming to Publish method in ChatBoxController.

def publish
    $redis.publish("chat_message:1:1", "#{params[:query]}")
    respond_to do |format|
        format.js {render nothing: true}
    end
end

..now i have a below background Resque job running with below code for testing purposes. So whenever a chat message is posted, its printing the data which is fine. But how can i add ActionController::Live feature to the background job ? or how do i go about this implementation ? Need help with this design.

class ChatBoxInitiator
    @queue = :chat_box_initiator

    private
    def self.perform
    $redis.subscribe('chat_message:1:1') do |on|
            on.message do |event, data|
                puts "====#{data}"
                return data
            end
        end
    end
end

and i want to show the Server Sent Events(SSE) along with ActionController::Live for notifications in Users/show page

like image 914
Rahul Dess Avatar asked Mar 19 '15 16:03

Rahul Dess


1 Answers

Pre-Reqs:

  • Ruby 2.0.0+
  • Rails 4.0.0+
  • Redis
  • Puma

Initializer:

Create a redis.rb initializer file in the config/initializers directory, globalizing an instance of redis. It's also a good idea to set up a heartbeat thread (Anything from 5 seconds to 5 minutes is okay, depending on your requirements):

$redis = Redis.new

heartbeat_thread = Thread.new do
  while true
    $redis.publish("heartbeat","thump")
    sleep 15.seconds
  end
end

at_exit do
  heartbeat_thread.kill
  $redis.quit
end

Controller:

You need to add two methods to your ChatController, pub and sub. The role of pub is to publish chat events and messages to redis, and sub to subscribe to these events. It should look something like this:

class ChatController < ApplicationController
    include ActionController::Live

    skip_before_filter  :verify_authenticity_token

    def index
    end

    def pub
        $redis.publish 'chat_event', params[:chat_data].to_json
        render json: {}, status: 200
    end

    def sub
        response.headers["Content-Type"] = "text/event-stream"

        redis = Redis.new
        redis.subscribe(['chat_event', 'heartbeat']) do |on|
            on.message do |event, data|
                response.stream.write "event: #{event}\ndata: #{data}\n\n"
            end
        end
    rescue IOError
        logger.info "Stream Closed"
    ensure
        redis.quit
        response.stream.close
    end
end

In your routes, make pub a POST and sub a GET, and match the path to something like /chat/publish and /chat/subscribe.


Coffeescript / Javascript:

Assuming your actual webpage for the chat app is at /chat, you need to write some Javascript to actually send and receive chat messages.

For ease of understanding, let's suppose your webpage only has a textbox and a button. Hitting the button should publish the content of the textbox to the chat stream, we can do that using AJAX:

$('button#send').click (e) ->
    e.preventDefault()
    $.ajax '/chat/publish',
        type: 'POST'
        data:
            chat_data: {
                message: $("input#message").val()
                timestamp: $.now()
        error: (jqXHR, textStatus, errorThrown) ->
            console.log "Failed: " + textStatus 
        success: (data, textStatus, jqXHR) ->
            console.log "Success: " + textStatus

Now, you need to be able to subscribe and receive the chat messages as well. You need to use EventSource for this. Using EventSource, open a channel for SSE so that you can receive events, and use that data to update the view. In this example, we will only log them to the javascript console.

The code should look something like this:

$(document).ready ->
    source = new EventSource('/chat/subscribe')
    source.addEventListener 'chat_event', (e) ->
        console.log(e.data)

Note: Place both of the code blocks above in your controllername.coffee file, for this example it should be chat.js.coffee in your app/assets/javascript directory. You also need to make sure it's being loaded in the asset pipeline. require it in your application.js file (if you aren't already calling require tree .).


Enable Parallel Requests:

In your development environment, you'll have to enable parallel requests by adding these two lines to your config/environments/development.rb:

config.preload_frameworks = true
config.allow_concurrency = true

Now fire up your browser, browse to /chat and see the magic. When you type a message and click the button, the message will be received by all instances of that webpage.


Well this is how you make a basic chat application in rails using ActionController::Live and Redis. The final code would obviously be very different depending on your requirements but this should get you started.

Some more resources you should check out:

  • Tender Love Making - Is it Live?
  • Railscasts - #401 - ActionController::Live
  • SitePoint - Mini Chat with Rails and SSEs
  • Github - mohanraj-ramanujam / live-stream
  • Thoughtbot - Chat Example using SSEs
like image 124
Sheharyar Avatar answered Oct 28 '22 22:10

Sheharyar