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

More Decks by Kevin Liebholz

Other Decks in Programming

Transcript

  1. 5JD

  2. "DUJPO$BCMFTFBNMFTTMZ JOUFHSBUFT 8FC4PDLFUT XJUIUIFSFTU PGZPVS3BJMTBQQMJDBUJPO*UBMMPXT GPSSFBMUJNFGFBUVSFTUPCFXSJUUFO JO3VCZJOUIFTBNFTUZMFBOEGPSN BTUIFSFTUPGZPVS3BJMTBQQMJDBUJPO  XIJMFTUJMMCFJOHQFSGPSNBOUBOE

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

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

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

    TDBMBCMF*UTBGVMMTUBDLPGGFSJOH UIBUQSPWJEFTCPUIBDMJFOUTJEF +BWB4DSJQUGSBNFXPSLBOEB TFSWFS TJEF3VCZGSBNFXPSL:PVIBWF BDDFTTUPZPVSFOUJSFEPNBJONPEFM XSJUUFOXJUI"DUJWF3FDPSEPSZPVS 03.PGDIPJDF
  6. 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
  7. HBNF TLFMFUPO  # @name, @character class Player < ApplicationRecord

    CHARACTERS = %w[dragon sword].freeze belongs_to :game before_create :assign_character # more code end
  8. HBNF TLFMFUPO  # @field1, @field2, …, @field9 class Game

    < ApplicationRecord has_many :players end
  9. 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 
  10. 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 
  11. 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 
  12. 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 
  13. 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 
  14. # 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 
  15. # 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 
  16. # 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 
  17. # 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 
  18. # 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 
  19. # 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 
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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