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

ActionCable and ReactJS tie the knot

ActionCable and ReactJS tie the knot

Yatish Mehta

June 03, 2016
Tweet

More Decks by Yatish Mehta

Other Decks in Technology

Transcript

  1. “What is the reason for this? There is no need

    or reason to do this. It is just a silly habit, they simply need a break!!”.
  2. $(document).ready(function() { setInterval(function() { $.ajax({ url: "/messages", type: "GET", success:

    function(data, textStatus, jqXHR) { //update div with new data } }); }, 5000); }); Polling
  3. “Action Cable seamlessly integrates WebSockets with the rest of your

    Rails application. It allows for real-time features to be written in Ruby in the same style and form as the rest of your Rails application”
  4. # app/channels/application_cable/connection.rb module ApplicationCable class Connection < ActionCable::Connection::Base identified_by :current_user

    def connect self.current_user = find_verified_user end protected def find_verified_user if current_user = User.find_by(id: cookies.signed[:user_id]) current_user else reject_unauthorized_connection end end end end
  5. // Action Cable provides the framework to deal with WebSockets

    in Rails. // You can generate new channels where WebSocket features live using the rails generate channel command. // //= require action_cable //= require_self //= require_tree ./channels this.App = {}; App.cable = ActionCable.createConsumer();
  6. App.messages = App.cable.subscriptions.create('NotificationChannel', { received(data) { // data sent from

    server is received here // { 'action': 'notification', 'body': ‘Event was created' } } })
  7. App.messages = App.cable.subscriptions.create('NotificationChannel', { received(data) { … customNotify(data) { this.perform('custom_notify',

    data); } }) App.messages.customNotify({ ‘action’ : ‘event' }) # app/channels/notification_channel.rb class NotificationChannel < ApplicationCable::Channel … def custom_notify(data) end end Client Server
  8. # app/channels/notification_channel.rb class NotificationChannel < ApplicationCable::Channel … def custom_notify(data) #

    data => { ‘action’: ‘event' } end end Client Server App.messages = App.cable.subscriptions.create('NotificationChannel', { received(data) { … customNotify(data) { this.perform('custom_notify', data); } }) App.messages.customNotify({ ‘action’ : ‘event' })
  9. MVC

  10. class HelloBox extends React.Component { render() { return ( <h1>

    Hello, Welcome to RubyNation </h1> ) } } ReactDOM.render(<HelloBox/>, document.getElementById('container'));
  11. class HelloBox extends React.Component { render() { return ( <h1>

    Hello, Welcome to RubyNation </h1> ) } } ReactDOM.render(<HelloBox/>, document.getElementById('container'));
  12. class HelloBox extends React.Component { render() { return ( <h1>

    Hello {this.props.name}, ({this.props.company}) Welcome to RubyNation </h1> ) } } let name = 'Yatish'; let company = 'Coupa'; ReactDOM.render(<HelloBox name={name} company={Coupa}/>, document.getElementById('container'));
  13. class HelloBox extends React.Component { render() { return ( <h1>

    Hello {this.props.name}, ({this.props.company}) Welcome to RubyNation </h1> ) } } let name = 'Yatish'; let company = 'Coupa'; ReactDOM.render(<HelloBox name={name} company={Coupa}/>, document.getElementById('container'));
  14. - component owns the state - data change via event

    handlers - re-render on state change (Virtual DOM) State
  15. var Counter = React.createClass({ getInitialState: function () { return {

    count: 0 }; }, handleClick: function () { this.setState({ count: this.state.count + 1, }); }, render: function () { return ( <button onClick={this.handleClick}> Click me! Number of clicks: {this.state.count} </button> ); } }); ReactDOM.render(<Counter />,document.getElementById('container'));
  16. var Counter = React.createClass({ getInitialState: function () { return {

    count: 0 }; }, handleClick: function () { this.setState({ count: this.state.count + 1, }); }, render: function () { return ( <button onClick={this.handleClick}> Click me! Number of clicks: {this.state.count} </button> ); } }); ReactDOM.render(<Counter />,document.getElementById('container'));
  17. var Counter = React.createClass({ getInitialState: function () { return {

    count: 0 }; }, handleClick: function () { this.setState({ count: this.state.count + 1, }); }, render: function () { return ( <button onClick={this.handleClick}> Click me! Number of clicks: {this.state.count} </button> ); } }); ReactDOM.render(<Counter />,document.getElementById('container'));
  18. class GameChannel < ApplicationCable::Channel def subscribed stream_from "player_#{uuid}" puts "Player

    #{uuid} has just joined" Seek.create(uuid) end def unsubscribed Seek.remove(uuid) end def make_move(data) Game.make_move(uuid, data) end end
  19. class Seek def self.create(uuid) opponent = REDIS.spop('players') if opponent Game.start(uuid,

    opponent) else REDIS.sadd('players', uuid) end end def self.remove(uuid) REDIS.srem('players', uuid) end end
  20. class Game def self.start(player1, player2) cross, nought = [player1, player2].shuffle

    ActionCable.server.broadcast "player_#{cross}", {action: "game_start", player: "X", msg: "Play your move!!"} ActionCable.server.broadcast "player_#{nought}", {action: "game_start", player: "O", msg: "Waiting for opponent to Play!!"} REDIS.set("opponent_for:#{cross}", nought) REDIS.set("opponent_for:#{nought}", cross) end def self.make_move(uuid, data) # data => {position: 3, player: 'X'} opponent = REDIS.get("opponent_for:#{uuid}") ActionCable.server.broadcast "player_#{opponent}", {action: "move", position: data['position'], player: data['player'], msg: "Play your move!!"} end end
  21. class Game def self.start(player1, player2) cross, nought = [player1, player2].shuffle

    ActionCable.server.broadcast "player_#{cross}", {action: "game_start", player: "X", msg: "Play your move!!"} ActionCable.server.broadcast "player_#{nought}", {action: "game_start", player: "O", msg: "Waiting for opponent to Play!!"} REDIS.set("opponent_for:#{cross}", nought) REDIS.set("opponent_for:#{nought}", cross) end def self.make_move(uuid, data) # data => {position: 3, player: 'X'} opponent = REDIS.get("opponent_for:#{uuid}") ActionCable.server.broadcast "player_#{opponent}", {action: "move", position: data['position'], player: data['player'], msg: "Play your move!!"} end end
  22. #board.js,jsx componentDidMount() { this.setupSocket() } setupSocket() { App.messages = App.cable.subscriptions.create('GameChannel',

    { received(data) { switch (data.action) { case "game_start": console.log('The game has started. You have been assigned a player.') this.setPlayer(data.player) this.updateMsg(data.msg) break; case "move": console.log("A move from opponent is received.") this.updateBoard(data.position, data.player) this.updateMsg(data.msg) break; } },
  23. render () { return ( <div> <p> You are player:

    {this.state.player} </p> <p> {this.state.msg} </p> <div id ='game'> { this.state.tiles.map(function(tile, index) { return ( <Tile state={tile} key={index} position={index} tileClickHandler={this.tileClickHandler.bind(this)} /> ) }, this) } </div> </div> ) }
  24. class Tile extends React.Component { clickHandler() { console.log(this.props) this.props.tileClickHandler(this.props.position); }

    render () { return ( <div className='tile' onClick={this.clickHandler.bind(this)}> {this.props.state} </div> ) } } —————————————————————————————————————————— #board.js tileClickHandler(position) { this.updateBoard(position, this.state.player); App.messages.makeMove({position: position, player: this.state.player}); this.updateMsg('Waiting for opponent to Play!!') }