Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Exploring the Power of Turbo Streams & Action Cable | RailsConf2023

Exploring the Power of Turbo Streams & Action Cable | RailsConf2023

Dive into the world of Turbo Streams and ActionCable with the Dragon Rider Eragon and his majestic dragon, Saphira, as we build a real-time tic-tac-toe game. We will utilize Turbo Stream broadcasting and ActionCable customization to create the game for our heroes, adding constraints of rising difficulty one after the other. Are you an advanced coder? Or are you a beginner? As long as you are looking to explore new applications of Hotwire’s Turbo or simply learn about it, we’re a match!

Kevin Liebholz

April 25, 2023
Tweet

Other Decks in Programming

Transcript

  1. &YQMPSJOHUIF1PXFSPG5VSCP
    4USFBNT"DUJPO$BCMF

    View Slide

  2. View Slide

  3. View Slide

  4. "MBHBËTJB

    View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. 3VCZPO3BJMT

    View Slide

  12. 3VCZPO3BJMT
    "DUJPO$BCMF

    View Slide

  13. 3VCZPO3BJMT
    "DUJPO$BCMF
    5VSCP4USFBNT

    View Slide

  14. View Slide

  15. 5JD

    View Slide

  16. 5JD
    5BD

    View Slide

  17. 5JD
    5BD
    5PF

    View Slide

  18. &YQMPSJOHUIF1PXFSPG5VSCP
    4USFBNT"DUJPO$BCMF

    View Slide

  19. 8IPBN*

    View Slide

  20. ,FWJO-JFCIPM[

    View Slide

  21. ,FWJO-JFCIPM[
    4PGUXBSF&OHJOFFS!

    View Slide

  22. ,FWJO-JFCIPM[
    4PGUXBSF&OHJOFFS!

    View Slide

  23. ,FWJO-JFCIPM[
    'JSTUUBML😱

    View Slide

  24. The Inheritance Cycle
    by Christopher Paolini

    View Slide

  25. 5VSCP4USFBNT
    "DUJPO$BCMF

    View Slide

  26. 5VSCP4USFBNT
    "DUJPO$BCMF
    .JTTJOHFYBNQMFT

    View Slide

  27. 5VSCP4USFBNT
    "DUJPO$BCMF
    .JTTJOHFYBNQMFT
    4QBSLJOH*NBHJOBUJPO

    View Slide

  28. View Slide

  29. *OHSFEJFOUT
    &YQMPSJOHUIF
    5IFPSZ

    View Slide

  30. $POTUSVDUJPO
    #VJMEJOHUIF
    (BNF
    *OHSFEJFOUT
    &YQMPSJOHUIF
    5IFPSZ

    View Slide

  31. *OHSFEJFOUT
    &YQMPSJOHUIF
    5IFPSZ
    4QFMM
    8BUDIJOHUIF&QJD
    (BNF
    $POTUSVDUJPO
    #VJMEJOHUIF
    (BNF

    View Slide

  32. *OHSFEJFOUT
    &YQMPSJOHUIF5IFPSZ

    View Slide

  33. 8FC4PDLFUT

    View Slide

  34. SFBMUJNF
    8FC4PDLFUT

    View Slide

  35. DMJFOU
    VT

    TFSWFS
    IFBERVBSUFS

    8FC4PDLFUT

    View Slide

  36. DMJFOU
    VT

    TFSWFS
    IFBERVBSUFS

    8FC4PDLFUT

    View Slide

  37. DMJFOU
    VT

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    TDPVU

    8FC4PDLFUT

    View Slide

  38. DMJFOU
    VT

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    TDPVU

    8FC4PDLFUT

    View Slide

  39. TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT

    8FC4PDLFUT

    View Slide

  40. TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT

    DMJFOU
    TDPVU

    8FC4PDLFUT

    View Slide

  41. "DUJPO$BCMF

    View Slide

  42. "DUJPO$BCMFTFBNMFTTMZ
    JOUFHSBUFT 8FC4PDLFUT XJUIUIFSFTU
    PGZPVS3BJMTBQQMJDBUJPO*UBMMPXT
    GPSSFBMUJNFGFBUVSFTUPCFXSJUUFO
    JO3VCZJOUIFTBNFTUZMFBOEGPSN
    BTUIFSFTUPGZPVS3BJMTBQQMJDBUJPO
    XIJMFTUJMMCFJOHQFSGPSNBOUBOE
    TDBMBCMF*UTBGVMMTUBDLPGGFSJOH
    UIBUQSPWJEFTCPUIBDMJFOUTJEF
    +BWB4DSJQUGSBNFXPSLBOEBTFSWFS
    TJEF3VCZGSBNFXPSL:PVIBWF
    BDDFTTUPZPVSFOUJSFEPNBJONPEFM
    XSJUUFOXJUI"DUJWF3FDPSEPSZPVS
    03.PGDIPJDF

    View Slide

  43. "DUJPO$BCMFTFBNMFTTMZ
    JOUFHSBUFT 8FC4PDLFUT XJUIUIFSFTU
    PGZPVS3BJMTBQQMJDBUJPO*UBMMPXT
    GPSSFBMUJNFGFBUVSFTUPCFXSJUUFO
    JO3VCZJOUIFTBNFTUZMFBOEGPSN
    BTUIFSFTUPGZPVS3BJMTBQQMJDBUJPO
    XIJMFTUJMMCFJOHQFSGPSNBOUBOE
    TDBMBCMF*UTBGVMMTUBDLPGGFSJOH
    UIBUQSPWJEFTCPUIBDMJFOUTJEF
    +BWB4DSJQUGSBNFXPSLBOEBTFSWFS
    TJEF3VCZGSBNFXPSL:PVIBWF
    BDDFTTUPZPVSFOUJSFEPNBJONPEFM
    XSJUUFOXJUI"DUJWF3FDPSEPSZPVS
    03.PGDIPJDF

    View Slide

  44. "DUJPO$BCMFTFBNMFTTMZ
    JOUFHSBUFT 8FC4PDLFUT XJUIUIFSFTU
    PGZPVS3BJMTBQQMJDBUJPO*UBMMPXT
    GPSSFBMUJNFGFBUVSFTUPCFXSJUUFO
    JO3VCZJOUIFTBNFTUZMFBOEGPSN
    BTUIFSFTUPGZPVS3BJMTBQQMJDBUJPO
    XIJMFTUJMMCFJOHQFSGPSNBOUBOE
    TDBMBCMF*UTBGVMMTUBDLPGGFSJOH
    UIBUQSPWJEFTCPUIBDMJFOUTJEF
    +BWB4DSJQUGSBNFXPSLBOEBTFSWFS
    TJEF3VCZGSBNFXPSL:PVIBWF
    BDDFTTUPZPVSFOUJSFEPNBJONPEFM
    XSJUUFOXJUI"DUJWF3FDPSEPSZPVS
    03.PGDIPJDF

    View Slide

  45. "DUJPO$BCMFTFBNMFTTMZ
    JOUFHSBUFT 8FC4PDLFUT XJUIUIFSFTU
    PGZPVS3BJMTBQQMJDBUJPO*UBMMPXT
    GPSSFBMUJNFGFBUVSFTUPCFXSJUUFO
    JO3VCZJOUIFTBNFTUZMFBOEGPSN
    BTUIFSFTUPGZPVS3BJMTBQQMJDBUJPO
    XIJMFTUJMMCFJOHQFSGPSNBOUBOE
    TDBMBCMF*UTBGVMMTUBDLPGGFSJOH
    UIBUQSPWJEFTCPUIBDMJFOUTJEF
    +BWB4DSJQUGSBNFXPSLBOEB TFSWFS
    TJEF3VCZGSBNFXPSL:PVIBWF
    BDDFTTUPZPVSFOUJSFEPNBJONPEFM
    XSJUUFOXJUI"DUJWF3FDPSEPSZPVS
    03.PGDIPJDF

    View Slide

  46. 5VSCP4USFBNT
    #SPBEDBTUJOH

    View Slide

  47. 5VSCP4USFBNT
    #SPBEDBTUJOH

    View Slide

  48. 5VSCP4USFBNT
    #SPBEDBTUJOH

    BCTUSBDUUIFBCTUSBDUJPO

    View Slide

  49. 5VSCP4USFBNT
    #SPBEDBTUJOH

    # app/models/concerns/turbo/broadcastable.rb
    module Turbo::Broadcastable
    extend ActiveSupport::Concern
    module ClassMethods
    def broadcast_remove_to # arguments
    # code
    end
    def broadcast_update_to # arguments
    # code
    end
    # even more methods
    end
    end
    UVSCPSBJMTHFN

    View Slide

  50. 5VSCP4USFBNT
    #SPBEDBTUJOH

    UVSCPSBJMTHFN
    # lib/turbo/engine.rb
    initializer "turbo.broadcastable" do
    ActiveSupport.on_load(:active_record) do
    include Turbo::Broadcastable
    end
    end

    View Slide

  51. QMVHQMBZ

    View Slide

  52. QMVHQMBZ
    FBTZUPVTF

    View Slide

  53. QMVHQMBZ
    FBTZUPVTF
    BVUPNBUJD%0.VQEBUFT

    View Slide

  54. View Slide

  55. $POTUSVDUJPO
    #VJMEJOHUIF(BNF

    View Slide

  56. UJDUBDUPF

    View Slide

  57. UJDUBDUPF

    View Slide

  58. UJDUBDUPF

    View Slide

  59. UJDUBDUPF

    View Slide

  60. UJDUBDUPF

    View Slide

  61. UJDUBDUPF

    View Slide

  62. UJDUBDUPF

    View Slide

  63. HBNF
    TLFMFUPO

    View Slide

  64. HBNF
    TLFMFUPO

    View Slide

  65. HBNF
    TLFMFUPO

    # @name, @character
    class Player < ApplicationRecord
    CHARACTERS = %w[dragon sword].freeze
    belongs_to :game
    before_create :assign_character
    # more code
    end

    View Slide

  66. HBNF
    TLFMFUPO

    # @field1, @field2, …, @field9
    class Game < ApplicationRecord
    has_many :players
    end

    View Slide

  67. DPOTUSBJOU

    View Slide

  68. DMJFOUTJOWPMWFE

    View Slide

  69. FGGFDUPGBDUJPO

    View Slide

  70. 6*EJGGFSFODFT

    View Slide

  71. QMBZFST
    BDUJPOPGPQQPOFOU
    DPOTUSBJOU

    View Slide

  72. DPOTUSBJOU

    View Slide

  73. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    DPOTUSBJOU

    View Slide

  74. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # games/show.html.erb
    <%= turbo_stream_from @player, "board" %>
    DPOTUSBJOU

    View Slide

  75. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # games/show.html.erb
    <%= turbo_stream_from @player, "board" %>
    DPOTUSBJOU

    View Slide

  76. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # games/show.html.erb
    <%= turbo_stream_from @player, "board" %>
    DPOTUSBJOU

    View Slide

  77. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    DPOTUSBJOU

    View Slide

  78. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # models/player.rb
    after_create :notify_opponent
    private
    def notify_opponent
    # code
    end
    DPOTUSBJOU

    View Slide

  79. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # models/player.rb
    after_create :notify_opponent
    private
    def notify_opponent
    broadcast_update_to [opponent, 'board’],
    # more to come
    end
    DPOTUSBJOU

    View Slide

  80. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # models/player.rb
    after_create :notify_opponent
    private
    def notify_opponent
    broadcast_update_to [opponent, 'board’],
    # more code
    end
    # games/show.html.erb
    <%= turbo_stream_from @player, "board" %>
    DPOTUSBJOU

    View Slide

  81. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # models/player.rb
    after_create :notify_opponent
    private
    def notify_opponent
    broadcast_update_to [opponent, 'board’],
    target: 'board’,
    # more code
    end
    DPOTUSBJOU

    View Slide

  82. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    DPOTUSBJOU

    View Slide

  83. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # models/player.rb
    after_create :notify_opponent
    private
    def notify_opponent
    broadcast_update_to [opponent, 'board’],
    target: 'board’,
    partial: 'games/board’,
    # more code
    end
    DPOTUSBJOU

    View Slide

  84. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # models/player.rb
    after_create :notify_opponent
    private
    def notify_opponent
    broadcast_update_to [opponent, 'board’],
    target: 'board’,
    partial: 'games/board’,
    locals: { … }
    # more code
    end
    DPOTUSBJOU

    View Slide

  85. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # models/player.rb
    after_create :notify_opponent
    private
    def notify_opponent
    broadcast_update_to [opponent, 'board’],
    target: 'board’,
    partial: 'games/board’,
    locals: { … }
    # more code
    end
    DPOTUSBJOU

    View Slide

  86. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    DPOTUSBJOU

    View Slide

  87. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    # models/player.rb
    after_create :notify_opponent
    private
    def notify_opponent
    broadcast_update_to [opponent, 'board’],
    target: 'board’,
    partial: 'games/board’,
    locals: { … }
    broadcast_update_to [opponent, 'board’],
    target: 'opponent_name’,
    partial: 'games/opponent_name’,
    locals: { … }
    end
    DPOTUSBJOU

    View Slide

  88. QMBZFST
    BDUJPOPGPQQPOFOU
    DPOTUSBJOU

    View Slide

  89. DPOTUSBJOU

    View Slide

  90. GJFME
    DPOTUSBJOU

    View Slide

  91. # views/games/_field.html.erb
    <% if # character%>
    <%= # show character %>
    <% else %>
    <%= form_with model: game do |f| %>
    <%= f.hidden_field :field_nr, value: field_nr %>
    <%= f.hidden_field :player_id, value: player.id %>
    <%= f.submit '' %>
    <% end %>
    <% end %>
    GJFME
    DPOTUSBJOU

    View Slide

  92. # views/games/_field.html.erb
    <%= turbo_frame_tag "field#{field_nr}" do %>
    <% if # already ticket %>
    <%= # show character %>
    <% else %>
    <%= form_with model: game do |f| %>
    <%= f.hidden_field :field_nr, value: field_nr %>
    <%= f.hidden_field :player_id, value: player.id %>
    <%= f.submit '' %>
    <% end %>
    <% end %>
    <% end %>
    GJFME
    DPOTUSBJOU

    View Slide

  93. # controllers/games_controller.rb
    def update
    @player = game.players.find(games_params[:player_id])
    game.update("field#{games_params[:field_nr]}”
    # more code
    end
    (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    DPOTUSBJOU

    View Slide

  94. (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    HBNFVQEBUFT
    DPOTUSBJOU

    View Slide

  95. (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    HBNFVQEBUFT
    DPOTUSBJOU

    PXO6*DIBOHFT

    View Slide

  96. # controllers/games_controller.rb
    def update
    @player = game.players.find(games_params[:player_id])
    game.update("field#{games_params[:field_nr]}”
    render partial: ‘field', locals: # some locals
    end
    (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    DPOTUSBJOU

    View Slide

  97. (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    HBNFVQEBUFT
    DPOTUSBJOU

    PXO6*DIBOHFT

    View Slide

  98. (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    HBNFVQEBUFT
    PQQPOFOU6*
    DIBOHFT
    PXO6*DIBOHFT
    DPOTUSBJOU

    View Slide

  99. # models/game.rb
    class Game < ApplicationRecord
    after_update :broadcast_tick_to_opponent
    def broadcast_tick_to_opponent
    # guard for when it was not a field update
    broadcast_replace_to [player.opponent, 'board’],
    target: field_tag_id,
    partial: 'games/opponent_tick’,
    locals: # some locals
    end
    end
    HBNFVQEBUFT
    DPOTUSBJOU

    View Slide

  100. # models/game.rb
    class Game < ApplicationRecord
    after_update :broadcast_tick_to_opponent
    def broadcast_tick_to_opponent
    # guard for when it was not a field update
    broadcast_replace_to [player.opponent, 'board’],
    target: field_tag_id,
    partial: 'games/opponent_tick’,
    locals: # some locals
    end
    end
    HBNFVQEBUFT
    DPOTUSBJOU

    View Slide

  101. # models/game.rb
    class Game < ApplicationRecord
    after_update :broadcast_tick_to_opponent
    def broadcast_tick_to_opponent
    # guard for when it was not a field update
    broadcast_replace_to [player.opponent, 'board’],
    target: field_tag_id,
    partial: 'games/opponent_tick’,
    locals: # some locals
    end
    end
    HBNFVQEBUFT
    DPOTUSBJOU

    View Slide

  102. # models/game.rb
    class Game < ApplicationRecord
    after_update :broadcast_tick_to_opponent
    def broadcast_tick_to_opponent
    # guard for when it was not a field update
    broadcast_replace_to [player.opponent, 'board’],
    target: field_tag_id,
    partial: 'games/opponent_tick’,
    locals: # some locals
    end
    end
    HBNFVQEBUFT
    DPOTUSBJOU

    View Slide

  103. (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    HBNFVQEBUFT
    PQQPOFOU6*
    DIBOHFT
    PXO6*DIBOHFT
    DPOTUSBJOU

    View Slide

  104. PQQPOFOU6*
    DIBOHFT
    PXO6*DIBOHFT
    DPOTUSBJOU

    View Slide

  105. PQQPOFOU6*
    DIBOHFT
    PXO6*DIBOHFT
    DPOTUSBJOU

    View Slide

  106. View Slide

  107. DPOTUSBJOU

    View Slide

  108. DPOTUSBJOU

    0/-:QMBZFST

    View Slide

  109. DPOTUSBJOU

    View Slide

  110. DPOTUSBJOU

    "DUJPO$BCMF$POOFDUJPO

    View Slide

  111. DPOTUSBJOU

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT
    DPOOFDUJPO

    View Slide

  112. DPOOFDUJPO
    DPOTUSBJOU

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT

    View Slide

  113. DPOOFDUJPO
    DPOTUSBJOU

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT

    View Slide

  114. DPOTUSBJOU

    # controllers/games_controller.rb
    def show
    if player
    cookies[:player_id] = player.id
    else
    # some other code
    end
    end

    View Slide

  115. DPOTUSBJOU

    # channels/application_cable/connection.rb
    module ApplicationCable
    class Connection < ActionCable::Connection::Base
    def connect
    player = Player.find_by(id: cookies['player_id’])
    reject_unauthorized_connection unless allow?(player)
    end
    end
    end

    View Slide

  116. DPOTUSBJOU

    View Slide

  117. DPOTUSBJOU

    View Slide

  118. View Slide

  119. FOEHBNFXIFOQMBZFSMFBWFT

    View Slide

  120. DPOTUSBJOU

    View Slide

  121. DPOTUSBJOU

    "DUJPO$BCMF$IBOOFM

    View Slide

  122. DPOTUSBJOU

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT
    DPOOFDUJPO
    DIBOOFM
    DIBOOFM
    DIBOOFM

    View Slide

  123. DPOTUSBJOU

    # channels/application_cable/channels.rb
    module ApplicationCable
    class Channel < ActionCable::Channel::Base
    end
    end

    View Slide

  124. DPOTUSBJOU

    # channels/turbo/streams_channel.rb
    class Turbo::StreamsChannel < ActionCable::Channel::Base
    extend Turbo::Streams::Broadcasts,
    Turbo::Streams::StreamName
    include Turbo::Streams::StreamName::ClassMethods
    # more code
    end
    UVSCPSBJMTHFN

    View Slide

  125. DPOTUSBJOU

    # channels/turbo/streams_channel.rb
    class Turbo::StreamsChannel < ActionCable::Channel::Base
    extend Turbo::Streams::Broadcasts,
    Turbo::Streams::StreamName
    include Turbo::Streams::StreamName::ClassMethods
    def subscribed
    if stream_name = verified_stream_name_from_params
    stream_from stream_name
    else
    reject
    end
    end
    end
    UVSCPSBJMTHFN

    View Slide

  126. DPOTUSBJOU

    # channels/game_channel.rb
    class GameChannel < ActionCable::Channel::Base
    extend Turbo::Streams::StreamName
    extend Turbo::Streams::Broadcasts
    include Turbo::Streams::StreamName::ClassMethods
    def subscribed
    # some subscribed logic that turbo wants
    end
    end

    View Slide

  127. DPOTUSBJOU

    View Slide

  128. DPOTUSBJOU

    DPOOFDU

    View Slide

  129. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF

    View Slide

  130. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF
    (BNF$IBOOFM
    TVCTDSJCFE

    View Slide

  131. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF
    (BNF$IBOOFM
    TVCTDSJCFE
    MFBWF

    View Slide

  132. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF
    (BNF$IBOOFM
    TVCTDSJCFE
    MFBWF
    (BNF$IBOOFM
    VOTVCTDSJCF

    View Slide

  133. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF
    (BNF$IBOOFM
    TVCTDSJCFE
    MFBWF
    (BNF$IBOOFM
    VOTVCTDSJCF
    (BNF$IBOOFM
    VOTVCTDSJCFE

    View Slide

  134. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF
    (BNF$IBOOFM
    TVCTDSJCFE
    MFBWF
    (BNF$IBOOFM
    VOTVCTDSJCF
    (BNF$IBOOFM
    VOTVCTDSJCFE
    QMBZFS
    CSPBEDBTU

    View Slide

  135. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF
    (BNF$IBOOFM
    TVCTDSJCFE
    MFBWF
    (BNF$IBOOFM
    VOTVCTDSJCF
    (BNF$IBOOFM
    VOTVCTDSJCFE
    PQQPOFOU6*
    DIBOHF
    QMBZFS
    CSPBEDBTU

    View Slide

  136. DPOTUSBJOU

    # views/games/show.html.erb
    <%= turbo_stream_from @player, "board", channel: GameChannel %>

    View Slide

  137. DPOTUSBJOU

    # channels/application_cable/connection.rb
    module ApplicationCable
    class Connection < ActionCable::Connection::Base
    def connect
    player = Player.find_by(id: cookies['player_id’])
    reject_unauthorized_connection unless allow?(player)
    end
    end
    end

    View Slide

  138. DPOTUSBJOU

    # channels/application_cable/connection.rb
    module ApplicationCable
    class Connection < ActionCable::Connection::Base
    identified_by :player
    def connect
    self.player = Player.find_by(id: cookies['player_id’])
    reject_unauthorized_connection unless allow?(player)
    end
    end
    end

    View Slide

  139. DPOTUSBJOU

    # channels/game_channel.rb
    class GameChannel < ActionCable::Channel::Base
    extend Turbo::Streams::StreamName
    extend Turbo::Streams::Broadcasts
    include Turbo::Streams::StreamName::ClassMethods
    def subscribed
    # some subscribed logic that turbo wants
    end
    end

    View Slide

  140. DPOTUSBJOU

    # channels/game_channel.rb
    class GameChannel < ActionCable::Channel::Base
    extend Turbo::Streams::StreamName
    extend Turbo::Streams::Broadcasts
    include Turbo::Streams::StreamName::ClassMethods
    def subscribed
    # some subscribed logic that turbo wants
    end
    def unsubscribed
    player.broadcast_unsubsciption
    end
    end

    View Slide

  141. DPOTUSBJOU

    # models/player.rb
    class Player < ApplicationRecord
    def broadcast_unsubsciption
    broadcast_update_to [opponent, 'board’],
    target: 'board’,
    html: 'Your opponent left the game.
    Be careful of surprise attacks’
    end
    end

    View Slide

  142. 4QFMM
    8BUDIJOHUIF&QJD(BNF

    View Slide

  143. View Slide

  144. 3BJMTJDB

    View Slide

  145. 3BJMTJDB
    5VSCPSJB

    View Slide

  146. 3BJMTJDB
    5VSCPSJB
    5JDr 5BDr 5PFSJDB

    View Slide

  147. DBTUJOH
    TVNNBSZ

    View Slide

  148. DBTUJOH
    TVNNBSZ
    8FC4PDLFUT à "DUJPO$BCMF à 5VSCP4USFBN

    View Slide

  149. DBTUJOH
    TVNNBSZ
    CBTJDCSPBEDBTUJOH

    View Slide

  150. DBTUJOH
    TVNNBSZ
    "DUJPO$BCMF$POOFDUJPOBEBQUJPO

    View Slide

  151. DBTUJOH
    TVNNBSZ
    "DUJPO$BCMF$IBOOFMBEBQUJPO

    View Slide

  152. View Slide

  153. $SFEJUT

    View Slide

  154. ,FWJO-JFCIPM[
    (JU)VCLFWLFW
    -JOLFE*OLFWJOMJFCIPM[
    5XJUUFS!,FWJO-JFCIPM[
    SVCZTPDJBM!,FWJO-JFCIPM[
    IUUQTHJUIVCDPNLFWLFWUVSCPTUSFBNBEWBODFE

    View Slide

  155. (JU)VCLFWLFW
    -JOLFE*OLFWJOMJFCIPM[
    5XJUUFS!,FWJO-JFCIPM[
    SVCZTPDJBM!,FWJO-JFCIPM[
    IUUQTHJUIVCDPNLFWLFWUVSCPTUSFBNBEWBODFE
    5IF&OE

    View Slide