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 full-size slide

  2. 3VCZPO3BJMT
    "DUJPO$BCMF

    View full-size slide

  3. 3VCZPO3BJMT
    "DUJPO$BCMF
    5VSCP4USFBNT

    View full-size slide

  4. &YQMPSJOHUIF1PXFSPG5VSCP
    4USFBNT"DUJPO$BCMF

    View full-size slide

  5. ,FWJO-JFCIPM[

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  8. ,FWJO-JFCIPM[
    'JSTUUBML😱

    View full-size slide

  9. The Inheritance Cycle
    by Christopher Paolini

    View full-size slide

  10. 5VSCP4USFBNT
    "DUJPO$BCMF

    View full-size slide

  11. 5VSCP4USFBNT
    "DUJPO$BCMF
    .JTTJOHFYBNQMFT

    View full-size slide

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

    View full-size slide

  13. *OHSFEJFOUT
    &YQMPSJOHUIF
    5IFPSZ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. *OHSFEJFOUT
    &YQMPSJOHUIF5IFPSZ

    View full-size slide

  17. SFBMUJNF
    8FC4PDLFUT

    View full-size slide

  18. DMJFOU
    VT

    TFSWFS
    IFBERVBSUFS

    8FC4PDLFUT

    View full-size slide

  19. DMJFOU
    VT

    TFSWFS
    IFBERVBSUFS

    8FC4PDLFUT

    View full-size slide

  20. DMJFOU
    VT

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    TDPVU

    8FC4PDLFUT

    View full-size slide

  21. DMJFOU
    VT

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    TDPVU

    8FC4PDLFUT

    View full-size slide

  22. TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT

    8FC4PDLFUT

    View full-size slide

  23. TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT

    DMJFOU
    TDPVU

    8FC4PDLFUT

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. 5VSCP4USFBNT
    #SPBEDBTUJOH

    View full-size slide

  29. 5VSCP4USFBNT
    #SPBEDBTUJOH

    View full-size slide

  30. 5VSCP4USFBNT
    #SPBEDBTUJOH

    BCTUSBDUUIFBCTUSBDUJPO

    View full-size slide

  31. 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 full-size slide

  32. 5VSCP4USFBNT
    #SPBEDBTUJOH

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

    View full-size slide

  33. QMVHQMBZ
    FBTZUPVTF

    View full-size slide

  34. QMVHQMBZ
    FBTZUPVTF
    BVUPNBUJD%0.VQEBUFT

    View full-size slide

  35. $POTUSVDUJPO
    #VJMEJOHUIF(BNF

    View full-size slide

  36. HBNF
    TLFMFUPO

    View full-size slide

  37. HBNF
    TLFMFUPO

    View full-size slide

  38. HBNF
    TLFMFUPO

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

    View full-size slide

  39. HBNF
    TLFMFUPO

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

    View full-size slide

  40. DMJFOUTJOWPMWFE

    View full-size slide

  41. FGGFDUPGBDUJPO

    View full-size slide

  42. 6*EJGGFSFODFT

    View full-size slide

  43. QMBZFST
    BDUJPOPGPQQPOFOU
    DPOTUSBJOU

    View full-size slide

  44. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    DPOTUSBJOU

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  48. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    DPOTUSBJOU

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  51. 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 full-size slide

  52. 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 full-size slide

  53. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    DPOTUSBJOU

    View full-size slide

  54. 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 full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. DPOOFDUTUSFBN OPUJGZPQQPOFOU
    DPOTUSBJOU

    View full-size slide

  58. 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 full-size slide

  59. QMBZFST
    BDUJPOPGPQQPOFOU
    DPOTUSBJOU

    View full-size slide

  60. GJFME
    DPOTUSBJOU

    View full-size slide

  61. # 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 full-size slide

  62. # 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 full-size slide

  63. # 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 full-size slide

  64. (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    HBNFVQEBUFT
    DPOTUSBJOU

    View full-size slide

  65. (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    HBNFVQEBUFT
    DPOTUSBJOU

    PXO6*DIBOHFT

    View full-size slide

  66. # 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 full-size slide

  67. (BNFT$POUSPMMFS
    VQEBUF
    GJFME
    TVCNJU
    HBNFVQEBUFT
    DPOTUSBJOU

    PXO6*DIBOHFT

    View full-size slide

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

    View full-size slide

  69. # 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 full-size slide

  70. # 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 full-size slide

  71. # 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 full-size slide

  72. # 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 full-size slide

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

    View full-size slide

  74. PQQPOFOU6*
    DIBOHFT
    PXO6*DIBOHFT
    DPOTUSBJOU

    View full-size slide

  75. PQQPOFOU6*
    DIBOHFT
    PXO6*DIBOHFT
    DPOTUSBJOU

    View full-size slide

  76. DPOTUSBJOU

    0/-:QMBZFST

    View full-size slide

  77. DPOTUSBJOU

    "DUJPO$BCMF$POOFDUJPO

    View full-size slide

  78. DPOTUSBJOU

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT
    DPOOFDUJPO

    View full-size slide

  79. DPOOFDUJPO
    DPOTUSBJOU

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT

    View full-size slide

  80. DPOOFDUJPO
    DPOTUSBJOU

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT

    View full-size slide

  81. DPOTUSBJOU

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

    View full-size slide

  82. 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 full-size slide

  83. FOEHBNFXIFOQMBZFSMFBWFT

    View full-size slide

  84. DPOTUSBJOU

    "DUJPO$BCMF$IBOOFM

    View full-size slide

  85. DPOTUSBJOU

    TFSWFS
    IFBERVBSUFS

    DMJFOU
    VT
    DPOOFDUJPO
    DIBOOFM
    DIBOOFM
    DIBOOFM

    View full-size slide

  86. DPOTUSBJOU

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

    View full-size slide

  87. 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 full-size slide

  88. 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 full-size slide

  89. 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 full-size slide

  90. DPOTUSBJOU

    DPOOFDU

    View full-size slide

  91. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF

    View full-size slide

  92. DPOTUSBJOU

    DPOOFDU
    (BNF$IBOOFM
    TVCTDSJCF
    (BNF$IBOOFM
    TVCTDSJCFE

    View full-size slide

  93. DPOTUSBJOU

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

    View full-size slide

  94. DPOTUSBJOU

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

    View full-size slide

  95. DPOTUSBJOU

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

    View full-size slide

  96. DPOTUSBJOU

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

    View full-size slide

  97. DPOTUSBJOU

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

    View full-size slide

  98. DPOTUSBJOU

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

    View full-size slide

  99. 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 full-size slide

  100. 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 full-size slide

  101. 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 full-size slide

  102. 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 full-size slide

  103. 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 full-size slide

  104. 4QFMM
    8BUDIJOHUIF&QJD(BNF

    View full-size slide

  105. 3BJMTJDB
    5VSCPSJB

    View full-size slide

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

    View full-size slide

  107. DBTUJOH
    TVNNBSZ

    View full-size slide

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

    View full-size slide

  109. DBTUJOH
    TVNNBSZ
    CBTJDCSPBEDBTUJOH

    View full-size slide

  110. DBTUJOH
    TVNNBSZ
    "DUJPO$BCMF$POOFDUJPOBEBQUJPO

    View full-size slide

  111. DBTUJOH
    TVNNBSZ
    "DUJPO$BCMF$IBOOFMBEBQUJPO

    View full-size slide

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

    View full-size slide

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

    View full-size slide