Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Sulman Baig
Sulman Baig

Posted on • Originally published atsulmanweb.com

     

Action Cable for Rails API

Rails 5 introduced us to action cable that is a web socket for rails.
ActionCable can be used for chats, synced editing app, or real-time app notifications.

When creating a new rails API or app, action cable comes builtin the app.
There are many articles on rails JS client and server action cable but not action cable the only server for rails API where the client can be any front end mobile app or web app.

Prerequisites for the Rails API

Assumptions for this article are:

  • Ruby 2.6.3
  • Rails 6.0.0 (API Only)
  • JWT user authentication implemented in the project
  • Logged In user can be identified ascurrent_user in the app.
  • There is a model namedConversation with two users nameduser_1 anduser_2.
  • Conversation has manymessages

Connecting Action Cable

Inapp/channels/connection.rb write a line:

identified_by:current_user
Enter fullscreen modeExit fullscreen mode

Make a private method of namedfind_verified_user it finds the current user and returns it else it returnsreject_unauthorized_connection. For example:

deffind_verified_usertokensession=Session.find_by(token:token)unlesssession.nil?returnsession.userelsereturnreject_unauthorized_connectionendend
Enter fullscreen modeExit fullscreen mode

Lastly, We are assuming that user token is coming in request as query params and we can write public method ofconnect:

defconnectself.current_user=find_verified_userrequest.params[:token]logger.add_tags'ActionCable',current_user.idend
Enter fullscreen modeExit fullscreen mode

Thisconnect method searches for the logged in user with right token or else rejects the connection to socket.

Subscribing the Conversation Channel

In terminal writerails g channel Conversation
Add a methodsubscribed, this method will get user to subscribe to the streams it is allowed to.

defsubscribedstop_all_streamsConversation.where(user_1:current_user).or(Conversation.where(user_2:current_user)).find_eachdo|conversation|stream_from"conversations_#{conversation.id}"endend
Enter fullscreen modeExit fullscreen mode

Also create a method namedunsubscribed to unsubscribe the user to all its streams:

defunsubscribedstop_all_streamsend
Enter fullscreen modeExit fullscreen mode

Send a message, register it and receive by other users

Now inconversation_channel.rb create a methodreceive that will be called when cable receives a message. This message will then be saved to database and then it will be broadcasted to the stream with conversation id so that other person can receive in realtime.

defreceive(data)@conversation=Conversation.find(data.fetch("conversation_id"))if(@conversation.user_1_id==current_user.id)||(@conversation.user_2_id==current_user.id)message_done=@conversation.messages.build(user_id:current_user.id)message_done.body=data["body"].present??data.fetch("body"):nilifmessage_done.saveMessageRelayJob.perform_later(message_done)endendend
Enter fullscreen modeExit fullscreen mode

Here, The message is received and if the data is correct then will be saved to database. Now a background service is called for sending this data back to socket in broadcast so that all the users in the subscribed streams get the message in realtime.
Themeessage_relay_job.rb will be like:

classMessageRelayJob<ApplicationJobqueue_as:defaultdefperform(message)data={}data["id"]=message.iddata["conversation_id"]=message.conversation_iddata["body"]=message.bodydata["user_id"]=message.user_iddata["created_at"]=message.created_atdata["updated_at"]=message.updated_atActionCable.server.broadcast"conversations_#{message.conversation_id}",data.as_jsonendend
Enter fullscreen modeExit fullscreen mode

Message Relay job is sent as background job is because important thing is to save the message in database if during job any connection issue comes the process can get halted. So even if the process is halted the other user can get message after refreshing the conversation and getting messages by REST or GraphQL.

Final Settings

Now that our conversation channel is ready for deployment. There are some settings to be made in the app.

  1. First create a route inconfig/routes.rb in root as:

    mountActionCable.server=>"/cable"
  2. As the action cable to be used by API and client is not on rails, go toconfig/application.rb and include:

    config.action_cable.disable_request_forgery_protection=trueconfig.action_cable.url="/cable"
  3. Installredis gem in the Gemfile as production can be only run in Redis server and also make sure server has redis installed and configured.

  4. Also setconfig/cable.yml according to your production server settings.

Run The WebSocket

Final run the rails server and the websocket will be available on the:

ws://{host+port}/cable?token={TOKEN}
Enter fullscreen modeExit fullscreen mode

Open the connection by some websocket test client and then send request:

{"command":"subscribe","identifier":{"channel":"ConversationChannel"}}
Enter fullscreen modeExit fullscreen mode

This command will subscribe to conversation stream in which it is authenticated to.
To send the message write:

{"command":"message","data":{"body":"Hello World","conversation_id":1,"action":"receive"},"identifier":{"channel":"ConversationChannel"}}
Enter fullscreen modeExit fullscreen mode

The message will be saved and will get receive to all of the subscribed user of the stream.

Unit Testing with RSpec

If you use RSpec Testing then create a filespec/channels/connection_spec.rb

require"rails_helper"RSpec.describeApplicationCable::Connection,:type=>:channeldoit"rejects if user not logged in"doexpect{connect"/cable"}.tohave_rejected_connectionexpect{connect"/cable?token=abcd"}.tohave_rejected_connectionendit"successfully connects"dosession=FactoryBot.create(:session)conversation=FactoryBot.create(:conversation,user_1_id:session.user_id)token=JsonWebToken.encode(user_id:session.user_id,token:session.token).to_sconnect"/cable?token=#{token}"expect(connection.current_user).toeqsession.userendend
Enter fullscreen modeExit fullscreen mode

Next create a filespec/channels/conversation_channel_spec.rb

require"rails_helper"RSpec.describeConversationChannel,type: :channeldoit"successfully subscribes"dosession=FactoryBot.create(:session)conversation=FactoryBot.create(:conversation,user_1_id:session.user_id)stub_connectioncurrent_user:session.usersubscribeexpect(subscription).tobe_confirmedexpect(subscription.current_user).toeqsession.userendit"successfully sends message"dosession=FactoryBot.create(:session)conversation=FactoryBot.create(:conversation,user_1_id:session.user_id)stub_connectioncurrent_user:session.usersubscribelast_count=Message.countperform:receive,{body:"lorem ipsum doler",conversation_id:conversation.id,attachment_id:nil}expect(Message.count).toeqllast_count+1endend
Enter fullscreen modeExit fullscreen mode

The Test should run fine.
Happy Coding!

Top comments(3)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
ndrean profile image
NDREAN
  • Joined

Hi! When you generate a Rails app --api-only, you don't get ActionCable set, don't you? How would you set it up?

CollapseExpand
 
sulmanweb profile image
Sulman Baig
Senior Software Engineer with 11 years of expertise in Ruby on Rails and Vue.js, specializing in health, e-commerce, staffing, and transport. Experienced in software development and version analysis.
  • Email
  • Location
    Multan, Pakistan
  • Education
    MS Software Engineering
  • Work
    Sr. Software Engineer #RubyOnRails #VueJS
  • Joined

I didn’t need action cable in the project. I will do in another tutorial of action cable using as api with redis. just action cable has to be pinged again and again as api connection gets closed unlike normal rails where page is continuously connected with the client.

CollapseExpand
 
ndrean profile image
NDREAN
  • Joined

I tried SSE with Rails -api only- and couldn't make the Redis publish/subscribe pattern to work. Yes, your solution will be nice.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Senior Software Engineer with 11 years of expertise in Ruby on Rails and Vue.js, specializing in health, e-commerce, staffing, and transport. Experienced in software development and version analysis.
  • Location
    Multan, Pakistan
  • Education
    MS Software Engineering
  • Work
    Sr. Software Engineer #RubyOnRails #VueJS
  • Joined

More fromSulman Baig

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp