module Entangled::Controller::InstanceMethods

Private Instance Methods

broadcast(&block) click to toggle source

Broadcast events to every connected client

# File lib/entangled/controller.rb, line 61
def broadcast(&block)
  # Use hijack to handle sockets
  hijack do |tubesock|
    begin
      # Assuming restful controllers, the behavior of
      # this method has to change depending on the action
      # it's being used in
      case action_name

      # If the controller action is 'index', a collection
      # of records should be broadcast
      when 'index'
        execute_action(tubesock, block)

        # The following code will run if an instance
        # variable with the plural resource name has been
        # assigned in yield. For example, if a
        # TacosController's index action looked something
        # like this:

        # def index
        #   broadcast do
        #     @tacos = Taco.all
        #   end
        # end

        # ...then @tacos will be broadcast to all connected
        # clients. The variable name, in this example,
        # has to be "@tacos"
        if collection
          redis_thread = Thread.new do
            redis.subscribe channel do |on|
              # Broadcast messages to all connected clients
              on.message do |channel, message|
                tubesock.send_data message
              end

              # Send message to whoever just subscribed
              tubesock.send_data({
                resources: collection
              }.to_json)

              close_db_connection
            end
          end

          # When client disconnects, kill the thread
          tubesock.onclose do
            redis_thread.kill
          end
        end

      # If the controller's action name is 'show', a single record
      # should be broadcast
      when 'show'
        execute_action(tubesock, block)

        # The following code will run if an instance variable
        # with the singular resource name has been assigned in
        # yield. For example, if a TacosController's show action
        # looked something like this:

        # def show
        #   broadcast do
        #     @taco = Taco.find(params[:id])
        #   end
        # end

        # ...then @taco will be broadcast to all connected clients.
        # The variable name, in this example, has to be "@taco"
        if member
          redis_thread = Thread.new do
            redis.subscribe channel do |on|
              # Broadcast messages to all connected clients
              on.message do |channel, message|
                tubesock.send_data message
              end

              # Send message to whoever just subscribed
              tubesock.send_data({
                resource: member
              }.to_json)

              close_db_connection
            end
          end

          # When client disconnects, kill the thread
          tubesock.onclose do
            redis_thread.kill
          end
        end

      # If the controller's action name is 'create', a record should be
      # created. Before yielding, the params hash has to be prepared
      # with attributes sent to the socket. The actual publishing
      # happens in the model's callback
      when 'create'
        tubesock.onmessage do |m|
          set_resource_params(m)
          execute_action(tubesock, block)

          # Send resource that was just created back to client. The resource
          # on the client will be overridden with this one. This is important
          # so that the id, created_at and updated_at and possibly other
          # attributes arrive on the client
          if member
            tubesock.send_data({
              resource: member
            }.to_json)
          end

          close_db_connection
        end

      # If the controller's action name is 'update', a record should be
      # updated. Before yielding, the params hash has to be prepared
      # with attributes sent to the socket
      when 'update'
        tubesock.onmessage do |m|
          set_resource_params(m)
          execute_action(tubesock, block)

          # Send resource that was just updated back to client. The resource
          # on the client will be overridden with this one. This is important
          # so that the new updated_at and possibly other attributes arrive
          # on the client
          if member
            tubesock.send_data({
              resource: member
            }.to_json)
          end

          close_db_connection
        end

      when 'destroy'
        tubesock.onmessage do |m|
          execute_action(tubesock, block)

          # Send resource that was just destroyed back to client
          if member
            tubesock.send_data({
              resource: member
            }.to_json)
          end

          close_db_connection
        end

      # For every other controller action, simply wrap whatever is
      # yielded in the tubesock block to execute it in the context
      # of the socket. Other custom actions can be added through this
      else
        tubesock.onmessage do |m|
          # If message was sent, attach to params (rescue exception if
          # message not valid JSON or message not present)
          params.merge!(JSON.parse(m)) rescue nil

          execute_action(tubesock, block)

          close_db_connection
        end
      end
    rescue Exception => e
      Rails.logger.error e
      close_db_connection
    end
  end
end
channel() click to toggle source

Infer channel from current path

# File lib/entangled/controller.rb, line 46
def channel
  request.path
end
close_db_connection() click to toggle source

Close the connection to the DB so as to not exceed the pool size. Otherwise, too many connections will be leaked and the pool will be exceeded

# File lib/entangled/controller.rb, line 54
def close_db_connection
  if ActiveRecord::Base.connection
    ActiveRecord::Base.connection.close
  end
end
collection() click to toggle source

Grabs @tacos

# File lib/entangled/controller.rb, line 36
def collection
  instance_variable_get(:"@#{resources_name}")
end
execute_action(tubesock, block) click to toggle source

Run the actual controller action that is passed as a block to the broadcast method and catch any exceptions as needed

# File lib/entangled/controller.rb, line 239
def execute_action(tubesock, block)
  begin
    # Execute block
    block.call
  rescue Exception => e
    # Log the error
    Rails.logger.error e.message

    # Print stack strace
    puts e.backtrace

    # Send error message to client
    tubesock.send_data({
      error: e.message
    }.to_json)
  end
end
member() click to toggle source

Grabs @taco

# File lib/entangled/controller.rb, line 41
def member
  instance_variable_get(:"@#{resource_name}")
end
model() click to toggle source

The model for this controller. E.g. Taco for a TacosController

# File lib/entangled/controller.rb, line 31
def model
  controller_name.classify.constantize
end
resource_name() click to toggle source

The singular name of the resource, inferred from the resources_name. This is used to infer the instance variable name for a single record assigned in the controller action

# File lib/entangled/controller.rb, line 26
def resource_name
  resources_name.singularize
end
resources_name() click to toggle source

The plural name of the resource, inferred from the controller’s name. For example, if it’s the TacosController, the resources_name will be “tacos”. This is used to infer the instance variable name for collections assigned in the controller action

# File lib/entangled/controller.rb, line 18
def resources_name
  controller_name
end
set_resource_params(message_from_socket) click to toggle source
# File lib/entangled/controller.rb, line 232
def set_resource_params(message_from_socket)
  params[resource_name.to_sym] = JSON.parse(message_from_socket)
end