首页 > 解决方案 > 带有 ActionCable (WS) 和 React 前端的 Rails API

问题描述

我已经通过 ActionCable 为我的 Rails API/React 前端应用程序设置了 WebSockets,但我正在为一些真正奇怪的功能而苦苦挣扎。目前,我<ActionCableConsumer />的随机射击。假设我打开了四个浏览器,我提交了一个应该向所有四个浏览器呈现新数据的表单,其中两个可以从通道流式传输数据,而另外两个什么也不呈现。或者其中一个浏览器可能会多次呈现数据,而其他三个浏览器则不呈现任何内容。这确实是一笔零星的交易。

这是我从订阅中获取数据并更新状态的地方:

    handleReceived = (message) => {
      console.log('check me')
      this.setState({
        apiData: [...this.state.apiData, message.pickup_delivery]
     });
    }


  render(){
    return(
      <div>
      <ActionCableConsumer channel={{channel: 'PickupDeliveriesChannel'}} onReceived={this.handleReceived}>
        <div>
          <CustomersForm showCustForm={this.state.showCustForm} handleClose={this.hideForm} addNewCustomer={this.addNewCustomer} />
          <Calendar getCustForm={this.getCustForm} getForm={this.getForm} deleteLoad={this.deleteLoad} {...this.state} seededColorGenerator={this.seededColorGenerator} />
          <PickupDeliveriesForm getFormColor={this.getFormColor} deleteCustomer={this.deleteCustomer} handleClose={this.hideForm} showForm={this.state.showForm} updateLoad={this.updateLoad} {...this.state} onNewLoad={this.addNewLoad} seededColorGenerator={this.seededColorGenerator} />
        </div>
      </ActionCableConsumer>
      </div>
    )
  }
}

这是我的控制器的样子:

module Api::V1
  class PickupDeliveriesController < ApplicationController
 before_action :set_pickup_delivery, only: [:show, :update, :destroy]
  # GET /pickup_deliveries
    def index
      @pickup_deliveries = PickupDelivery.all

      render json: @pickup_deliveries
    end

  # GET /pickup_deliveries/1
    def show
      render json: @pickup_delivery
    end

  # POST /pickup_deliveriesPickupDeliveries
    def create
    pickup_deliveries = PickupDelivery.new(pickup_delivery_params)
    if pickup_deliveries.save
      serialized_data = ActiveModelSerializers::Adapter::Json.new(
        PickupDeliveriesSerializer.new(pickup_deliveries)
      ).serializable_hash
      ActionCable.server.broadcast 'pickup_deliveries_channel', serialized_data
      head :ok
    end
  end

  # PATCH/PUT /pickup_deliveries/1
    def update
      if @pickup_delivery.update(pickup_delivery_params)
        render json: @pickup_delivery
      else
        render json: @pickup_delivery.errors, status: :unprocessable_entity
      end
    end

  # DELETE /pickup_deliveries/1
    def destroy
      @pickup_delivery.destroy
    end

    private
    # Use callbacks to share common setup or constraints between actions.
      def set_pickup_delivery
       @pickup_delivery = PickupDelivery.find(params[:id])
      end

    # Only allow a trusted parameter "white list" through.
      def pickup_delivery_params
        params.require(:pickup_delivery).permit(:id, :pickup_date, :pickup_location, :rate, :delivery_date, :delivery_location, :local_delivery, :local_pickup, :loaded_miles, :deadhead_miles, :delivery_id, :pickup_zip, :delivery_zip, :hazmat, :sameday, :delivery_time, :pickup_time, :aurora_number, :out_of_route, :round_trip, :customer_id, :color)
      end
  end
end

这是我的实际频道:

class PickupDeliveriesChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'pickup_deliveries_channel'
  end
end

我没有指定任何用户,因为我还不需要。基本上,当客户在负载板上提交新负载时,我需要所有其他负载板来显示新实例。目前,它并没有像那样工作。

打开五个浏览器并在随机浏览器上提交一个表单后,五个浏览器中的三个呈现正确的数据(一个加载),一个浏览器不呈现任何内容,一个浏览器呈现数据两次。

这是纯粹的混乱哈哈。

PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
PickupDeliveriesChannel is transmitting the subscription confirmation
PickupDeliveriesChannel is streaming from pickup_deliveries_channel
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
PickupDeliveriesChannel is transmitting the subscription confirmation
PickupDeliveriesChannel is streaming from pickup_deliveries_channel
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
Could not execute command from ({"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"PickupDeliveriesChannel\"}"}) [RuntimeError - Unable to find subscription with identifier: {"channel":"PickupDeliveriesChannel"}]: C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb:78:in `find' | C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb:46:in `remove' | C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb:18:in `execute_command' | C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/base.rb:87:in `dispatch_websocket_message' | C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/worker.rb:60:in `block in invoke'
PickupDeliveriesChannel is transmitting the subscription confirmation
PickupDeliveriesChannel is streaming from pickup_deliveries_channel
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
PickupDeliveriesChannel is transmitting the subscription confirmation
PickupDeliveriesChannel is streaming from pickup_deliveries_channel

在那个特定的提交中,这是我的 rails 控制台的样子。

这是纯粹的混乱,我不知道如何让它正确流动。

编辑:我提交的表单越多,打开的订阅就越多。几乎就像每次我打开表单提交内容时,另一个订阅正在流式传输?打开两个浏览器,我可以传输任意数量的对象...

这是我的路线:

Rails.application.routes.draw do
    namespace :api do
      namespace :v1 do
        resources :pickup_deliveries
        resources :customers
        end
    end
    mount ActionCable.server => '/cable'
end

这是我的 index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ActionCableProvider } from 'react-actioncable-provider';
import * as serviceWorker from './serviceWorker';
import { API_WS_ROOT } from './constants';

ReactDOM.render(
  <ActionCableProvider url={API_WS_ROOT}>
    <App />
  </ActionCableProvider>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();

这是我正在使用的常量

export const API_ROOT = 'http://localhost:3001/api/v1';
export const API_WS_ROOT = 'ws://localhost:3001/cable';
export const HEADERS = {
  'Content-Type': 'application/json',
  Accept: 'application/json',
};

标签: reactjsformsrails-apiactioncable

解决方案


所以,我想通了。我将包含我所有params从前端获取的实例 VIA 的当前状态发送到通道,并将其与当前数据库进行比较。老实说,我不确定它为什么起作用。基本上,我说如果params[:all_loads](前端当前获取的响应)等于后端对象中的实例数量,则继续流式传输到通道。解决方案如下:

class PickupDeliveriesChannel < ApplicationCable::Channel
  def subscribed
    if params[:all_loads].length == PickupDelivery.all.length
        stream_from 'pickup_deliveries_channel'
    end
  end

  def unsubscribed

  end
end

推荐阅读