Skip to content

Using the DataStore

Dan Knox edited this page Feb 27, 2013 · 5 revisions

The DataStore provides a convenient way to temporarily persist information between execution of events. Since every event is executed within a new instance of the controller class, instance variables set while processing an action will be lost after the action finishes executing.

There are two different DataStore classes that you can use:

The DataStore::Controller class is unique for every controller. You can use it similar to how you would use instance variables within a plain ruby class. The values set within the controller store will be persisted between events. The controller store can be accessed within your controller using the #controller_store method.

The DataStore::Connection class is unique for every active connection. You can use it similar to the Rails session store. The connection data store can be accessed within your controller using the #connection_store method.

The Controller DataStore

Unlike the connection data store which behaves very much like the Rails session store, the Controller DataStore is slightly unique. The basic premise is to act as a stand-in for instance variables in your controller. At it's core, it is a Hash which is accessible inside your controller through the #controller_store instance method. Any values set in the controller store will be visible by all connected users which trigger events that use that controller. However, values set in one controller will not be visible by other controllers.

Lets take a look at a very contrived use case. Here we are going to keep a counter to track the number of events that have been triggered within a particular controller.

class AccountController < WebsocketRails::BaseController
  # Set the initial event count value to 0
  def initialize_session
    controller_store[:event_count] = 0
  end

  # Mapped as `accounts.important_event` in the Event Router
  def important_event
    # This will be private for each controller
    controller_store[:event_count] += 1
    trigger_success controller_store[:event_count]
  end
end

class ProductController < WebsocketRails::BaseController
  # Set the initial event count value to 0
  def initialize_session
    controller_store[:event_count] = 0
  end

  # Mapped as `products.boring_event` in the Event Router
  def boring_event 
    controller_store[:event_count] += 1
    trigger_success controller_store[:event_count]
  end
end

Lets take a look at the responses received when a user begins to trigger these events.

In the interest of brevity, I am going to leave out the JavaScript to trigger the events and handle the response.

# trigger `accounts.important_event`
=> 1

# trigger `accounts.important_event`
=> 2

# trigger `products.boring_event`
=> 1

As you can see, incrementing the :event_count key in the controller store for AccountController had no effect on the ProductController event count.

Now if another user decides to trigger the accounts.important_event immediately after the first user triggered the events above, they would increment the count from 2 to 3 since the controller store is shared amongst all connected users.

If you wish to store private data for a user, you can use the connection data store which is explained further below.

Collecting Data From All Stores DataStore#collect_all

Sometimes it is useful to be able to collect information from all of your data stores. For this we have the #collect_all method. Like it's name suggests, this method will collect all of the values for a given key from every currently initialized data store and return them as an array or yield them to a block if one is supplied.

Continuing the example from above, we could use #collect_all to retrieve a total event count across both controllers.

event_counts = controller_store.collect_all(:event_count)
=> [3, 1]

event_total = controller_store.collect_all(:event_count).reduce(:+)
=> 4

Assuming User B had actually triggered the important_event a third time, we now have the counts from AccountsController and ProductController wrapped up and returned to us inside of an array.

If we pass #collect_all a block, each value will be yielded.

controller_store.collect_all(:event_count) { |count| puts count }
=> 3
=> 1

One small caveat to the #collect_all method arises when load balancing between multiple instances of the standalone server. Currently, #collect_all will only return the values from the data stores that have been initialized within the instance of the standalone server that the current user is connected to. If your production environment is currently configured this way I apologize. I will have a Redis backed data store implemented soon which will solve this issue.

The Connection Data Store

The connection data store operates much like the controller store. The biggest difference is that the data placed inside is private for individual users and accessible from any controller. Anything placed inside the connection data store will be deleted when a user disconnects.

The connection data store is accessed through the #connection_store instance method inside your controller.

If we were writing a basic chat system, we could use the connection data store to hold onto a user's current screen name.

class UserController < WebsocketRails::BaseController
  
  def set_screen_name
    connection_store[:screen_name] = message[:screen_name]
  end

end

class ChatController < WebsocketRails::BaseController

  def say_hello
    screen_name = connection_store[:screen_name]
    send_message :new_message, "#{screen_name} says hello"
  end

end

Like the controller data store, you can use the #collect_all method explained above to retrieve values from the connection stores of all connected users.

users = connection_store.collect_all(:users)
=> [:othello, :snoop_dogg]

Of course, the same caveats to the collect all method regarding load balanced standalone servers currently apply to the connection data store as well.