websocket-rails/websocket-rails

View on GitHub
lib/websocket_rails/base_controller.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require "websocket_rails/data_store"
require 'abstract_controller/callbacks'

module WebsocketRails
  # Provides controller helper methods for developing a WebsocketRails controller. Action methods
  # defined on a WebsocketRails controller can be mapped to events using the {EventMap} class.
  #
  # == Example WebsocketRails controller
  #   class ChatController < WebsocketRails::BaseController
  #     # Can be mapped to the :client_connected event in the events.rb file.
  #     def new_user
  #       send_message :new_message, {:message => 'Welcome to the Chat Room!'}
  #     end
  #   end
  #
  # It is best to use the provided {DataStore} to temporarily persist data for each client between
  # events. Read more about it in the {DataStore} documentation.
  #
  #
  class BaseController

    # We need process_action to be in a module loaded before AbstractController::Callbacks
    # to get inheritance properly
    module Metal
      def process_action(method, event)
        if respond_to?(method)
          self.send(method)
        else
          raise EventRoutingError.new(event, self, method)
        end
      end
      def response_body
        false
      end
    end

    include Metal
    include AbstractController::Callbacks

    # Tell Rails that BaseController and children can be reloaded when in
    # the Development environment.
    def self.inherited(controller)
      unless controller.name == "WebsocketRails::InternalController" || Rails.version =~/^4/
        unloadable controller
      end
    end

    # Tell the dispatcher to use channel filtering on specific channels.
    # If supplied, the :catch_all => :method will be processed for every
    # event that comes into the channel(s).
    #
    #
    # Example:
    # To process events based upon the event_name inside :channel_one
    #
    #  filter_for_channels :channel_one
    #
    # To process events based upon the event_name and a catch all
    #
    #  filter_for_channels :channel_one, :catch_all => :logger_method
    #
    def self.filter_for_channels(*channels)
      options = channels.last.is_a?(Hash) ? channels.pop : {}
      channels.each do |channel|
        WebsocketRails.filtered_channels[channel] = options[:catch_all].nil? ? self : [self, options[:catch_all]]
      end
    end

    # Provides direct access to the connection object for the client that
    # initiated the event that is currently being executed.
    def connection
      @_event.connection
    end

    # The numerical ID for the client connection that initiated the event. The ID is unique
    # for each currently active connection but can not be used to associate a client between
    # multiple connection attempts.
    def client_id
      connection.id
    end

    # The {Event} object that triggered this action.
    # Find the current event name with event.name
    # Access the data sent with the event with event.data
    # Find the event's namespace with event.namespace
    def event
      @_event
    end

    # The current message that was passed from the client when the event was initiated. The
    # message is typically a standard ruby Hash object. See the README for more information.
    def message
      @_event.data
    end
    alias_method :data, :message

    # Trigger the success callback function attached to the client event that triggered
    # this action. The object passed to this method will be passed as an argument to
    # the callback function on the client.
    def trigger_success(data=nil)
      event.success = true
      event.data = data
      event.trigger
    end

    # Trigger the failure callback function attached to the client event that triggered
    # this action. The object passed to this method will be passed as an argument to
    # the callback function on the client.
    def trigger_failure(data=nil)
      event.success = false
      event.data = data
      event.trigger
    end

    def accept_channel(data=nil)
      channel_name = event.data[:channel]
      WebsocketRails[channel_name].subscribe connection
      trigger_success data
    end

    def deny_channel(data=nil)
      trigger_failure data
    end

    def stop_event_propagation!
      event.propagate = false
    end

    # Sends a message to the client that initiated the current event being executed. Messages
    # are serialized as JSON into a two element Array where the first element is the event
    # and the second element is the message that was passed, typically a Hash.
    #
    # To send an event under a namespace, add the `:namespace => :target_namespace` option.
    #
    #   send_message :new_message, message_hash, :namespace => :product
    #
    # Nested namespaces can be passed as an array like the following:
    #
    #   send_message :new, message_hash, :namespace => [:products,:glasses]
    #
    # See the {EventMap} documentation for more on mapping namespaced actions.
    def send_message(event_name, message, options={})
      options.merge! :connection => connection, :data => message
      event = Event.new( event_name, options )
      @_dispatcher.send_message event if @_dispatcher.respond_to?(:send_message)
    end

    # Broadcasts a message to all connected clients. See {#send_message} for message passing details.
    def broadcast_message(event_name, message, options={})
      options.merge! :connection => connection, :data => message
      event = Event.new( event_name, options )
      @_dispatcher.broadcast_message event if @_dispatcher.respond_to?(:broadcast_message)
    end

    def request
      connection.request
    end

    def action_name
      @_action_name
    end

    # Provides access to the {DataStore} for the current controller. The {DataStore} provides convenience
    # methods for keeping track of data associated with active connections. See it's documentation for
    # more information.
    def controller_store
      @_controller_store
    end

    def connection_store
      connection.data_store
    end

    def self.controller_name
      self.name.underscore.gsub(/_controller$/,'')
    end

    def controller_name
      self.class.controller_name
    end

    private

    def delegate
      connection.controller_delegate
    end

    def method_missing(method,*args,&block)
      if delegate.respond_to? method
        delegate.send method, *args, &block
      else
        super
      end
    end

  end
end