Let’s explore how to integrate Rails ActionCable functionality (WebSockets) with a basic chat application using React and Redux (via Redux Toolkit). I’m only including the most relevant snippets of code, please refer to the code in the repo for the entire context.
Since I’m using rails as an API endpoint, I’ll create the app using the --api flag. This will prevent views from being generated when we call any of the rails generate commands, hence avoiding unnecessary code. Additionally, we’ll use postgresql as the DB.
1
rails new chat-app-backend-rails --api -database=postgresql
Since we’re building our frontend as a separate standalone project, potentially deployed on a different server than our API, we need to allow for cross domain calls. For that, we first add rack-cors on the Gemfile:
1
gem'rack-cors'
And then configure it on config/initializers/cors.rb.
1
2
3
4
5
6
7
8
9
10
11
Rails.application.config.middleware.insert_before0,Rack::Corsdoallowdo# In a prod app you'll restrict to specific origin(s).# for this will just allow from any.origins'*'resource'*',headers::any,methods:%i[getpostputpatchdeleteoptionshead]endend
We then bundle install to install the gem we added.
Our app will simply have User and Messages. Let’s create the models for that:
1
2
rails generate model User
rails generate model Message
Our User will only have username and status this is what the migration looks like:
Our models have a 1-to-many relationship (1 user has many messages). We’ll capture that by adding has_many :messages on the User and belongs_to on Message.
That’s it for the setup. We’re now ready to start adding some functionality. Let’s start creating the messages and users channels. We’ll use these to listen for messages posted on the chat and for users joining.
Now we can use the ActionCable.server.broadcast() method to broadcast to all the subscribers on those channels. We want to notify to all subscribers of the user_channel when a user joins the chat. We also want to notify the message_channel after sending messages. Let’s do both of those things on the UsersController:
For completion, we also have our MessagesController that returns all messages for the users who just joined the chat (that way they can see what was said before them joining).
This template will give you an application skeleton that uses redux with toolkit already setup (e.g., a sample reducer, a configured store, etc.).
I’ll start by creating a /features/users folder. In there I’ll add all the api and reducer functionality. In there I created a usersAPI with all the backend calls related to users. For example, this is how we’re adding a new user to the chat:
We will use these API calls indirectly via Redux thunks.
When working with async calls in the frontend, we usually make the async call and if it succeeds, we update the application state (e.g., Redux state) with the results. With thunks, the process is the same, but all is handled in the reducer itself. We only have to dispatch an action and after is fulfilled (e.g., call succeeded) then we update the state.
This is what a thunk looks like for adding a new user and for sending messages: