What Jacob Did - Xiaohong-Deng/mooqita-icccg GitHub Wiki

Jacob is the one who contributed the very first ActionCable involved feature to this project, which is the game-waiting-room feature. The control flow with AC involved is more complicated than what typically happens in Rails applications.

The Control Flow for game-waiting-room

The control flow is initiated by clicking the button New Game, sending a http request. Next I will explain each component the control is passed through along the way, until it hits the very first page in the gaming cycle.

game_waiting_room_controller.rb

By default, no index method means render index.html.erb. authenticate_user! is a method provided by devise

view/game_waiting_room/index.html.erb

In this file, there is just one piece of javascript code. Its duty is to call the subscription function to let the user to subscribe to GameWaitingRoomChannel. The subscription function is in a javascript/coffeescript file inside asset pipeline folders. This cause the js file to be loaded on every page. So if we directly subscribe there, we are letting authenticated users subscribe to GameWaitingRoomChannel on every page in the app. The action of subscribing is calling App.cable.subscriptions.create which create a subscription instance. Encapsulating this function call in another function and call that function on a specific page makes the channel private to the page.

channels/game_waiting_room_channel.rb

When a user subscribes, it triggers GameWaitingRoomChannel#subscribed


  def subscribed
    stream_for current_user
    stream_from 'waiting-room'

    add_current_user
  end

  private

  def add_current_user
    GameWaitingRoom.add(current_user)
    @game = create_game if GameWaitingRoom.full?
    CurrentUserBroadcastJob.perform_later(current_user, @game)
    ParticipantsBroadcastJob.perform_now(@game)
  end

We add current_user to GameWaitingRoom. Note in Jacob's design we only have one waiting room at any moment. If the room has 3 players by far we create a new instance in database and assign it to the instance variable @game. Then we handle updating info on the individual player's page to ActiveJob.

jobs/current_user_broadcast_job.rb

  include Templatable
  def perform(user, game)
    GameWaitingRoomChannel.broadcast_to user, data_for(game)
  end

  private

  def data_for(game)
    game ? game_starting(game) : game_waiting
  end

data_for(game) is a hash from which client-side js code for ActionCable can retrieve info broadcasted through the channel. Here game_starting and game_waiting are methods from module Templatable. participants_broadcast_job.rb does pretty much the same. I'll leave that to you.

jobs/concerns/templatable.rb

It provides methods that return a hash containing two key-val pairs. One is a html snippet and the other is the html tag into which the snippet is about to be inserted. Depending on the path you will notice that views/game_waiting_room/_waiting.html.erb and view/game_waiting_room/_starting.html.erb will be rendered and put in the hash.

Now the data is ready to be broadcasted through the channel. Let's see how the client-side js code will handle the data coming through.

asset/javascripts/game_waiting_room.coffee


window.startGameWaitingRoomCable = ->
  App.game = App.cable.subscriptions.create "GameWaitingRoomChannel",
    received: (data) ->
      @renderTemplate(data)
    renderTemplate: (data) ->
      target = $(data['target'])
      # $().html() replace the content of the tag
      # with the content in html()
      target.html(data['template'])

It simply replaces the text inside the target html tag with the data we rendered in Templatable

The control flow ends here. Or does it?

More to Come with This Control Flow

models/game_waiting_room.rb

In GameWaitingRoomChannel we see some class method calls on GameWaitingRoom. It's a non-persistent model with a redis queue to serve as the room. It defines a lot of instance methods and delegate the class method calls with the same name to a instance inside the class. It's a very convoluted approach. Rails provided helper delegate is implemented in a way that by default you can only delegate instance methods to instance methods. Check out the source code delegation.rb. In order to delegate class methods to class methods, Jacob opened singleton class inside the class definition.

views/game_waiting_room/_starting.html.erb

Once the room is full, this html snippet will replace the waiting room page. It also contains a piece of js code which sends a http request causing you to be redirected to the first page of the gaming cycle. So the journey begins.