Rails 5 actioncable as a standalone server - ruby-on-rails

There's not TOO much documentation on action cable so I'm a little lost on this.
I'm playing with a rails 5 app, and I'm trying to use the rails5 app as purely an api and hosting my JS elsewhere. So when I start my actioncable server, i am able to connect to the websocket pretty easily just using my built in browser socket support with:
var socket = new WebSocket('localhost:3000/cable')
// and then do
socket.onmessage = function(data) { console.log(data) }
I connect successfully. I'm getting pings in the form of
MessageEvent {isTrusted: true, data: "{"type":"ping","message":1462992407}", ... etc
Except I can't seem to broadcast any messages down to the client. I tried:
ActionCable.server.broadcast('test',{ yes: true })
But only the pings come in. ActionCable comes with its own concepts that I haven't wrapped my head around fully yet like channels and stuff which "just work" in rails apps. But how can I successfully build a separate standalone JS app using actioncable's socket server?

I use ActionCable with an iOS application. Everything works just fine.
ActionCable uses pub/sub pattern.
Pub/Sub, or Publish-Subscribe, refers to a message queue paradigm
whereby senders of information (publishers), send data to an abstract
class of recipients (subscribers), without specifying individual
recipients. Action Cable uses this approach to communicate between the
server and many clients.
Which means that you should create a new channel first,
rails g channel my_channel
Then in your channel send some test message:
# app/channels/my_channel.rb
class MyChannel < ApplicationCable::Channel
def subscribed
stream_from "my_channel"
ActionCable.server.broadcast "my_channel", 'Test message'
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
Then send the following to your server:
{'command': 'subscribe', 'identifier': {\'channel\':\'MyChannel\'}}
In return you'll get your first frame.

Related

How to organize delayed results processing in microservices communication?

I have the following system: my Rails server issues commands to the Flask server and the latest one responses immediately with status 200. After that Flask server runs a background task with some time-consuming function. After a little while, it comes up with some results and designed to send data back to the Rails server via HTTP (see diagram)
Each Flask data portion can affect several Rails models (User, Post etc...). Here I faced with two questions:
How I should structure my controllers/actions on the Rails side in this case? Currently, I think about one controller and each action of it corresponds to Python 'delayed' data portion.
Is it a normal way of microservices communication? Or I can organize it in a different, more simple way?
This sounds like pretty much your standard webhook process. Rails pokes Flask with a GET or POST request and Flask pokes back after a while.
For example lets say we have reports, and after creating the report we need flask to verify the report:
class ReportsController
# POST /reports
def create
#report = Report.new(report_params)
if #report.save
FlaskClient.new.verify(report) # this could be delegated to a background job
redirect_to #report
else
render :new
end
end
# PATCH /reports/:id/verify
def verify
# process request from flask
end
end
class FlaskClient
include Httparty
base_uri 'example.com/api'
format 'json'
def verify(report)
self.class.post('/somepath', data: { id: report.id, callback_url: "/reports/#{report.id}/verify", ... })
end
end
Of course the Rails app does not actually know when Flask will respond or that Flask and the background service are different. It just sends and responds to http requests. And you definitely don't want rails to wait around so save what you have and then later the hook can update the data.
If you have to update the UI on the Rails side without the user having to refresh manually you can use polling or websockets in the form of ActionCable.

How to get IDs of models subscribed to via a specific channel

How do I get a list of all ActiveRecord models currently subscribed to via a specific ActionCable Channel?
I'm sorry to give you an answer you do not want, but...:
You don't get a list of all subscribed clients. You shouldn't be able to. If you need this information, you might be experiencing a design flaw.
Why?
The pub/sub paradigm is designed to abstract these details away, allowing for horizontal scaling in a way that has different nodes managing their own subscription lists.
Sure, when you're running a single application on a single process, you might be able to extract this information - but the moment you scale up, using more processes / machines, this information is distributed and isn't available any more.
Example?
For example, when using iodine's pub/sub engine (see the Ruby iodine WebSocket / HTTP server for details):
Each process manages it's own client list.
Each process is a "client" in the master / root process.
Each master / root process is a client in a Redis server (assuming Redis is used).
Let's say you run two iodine "dynos" on Heroku, each with 16 workers, then:
Redis sees a maximum of two clients per channel.
Each of the two master processes sees a maximum of 16 clients per channel.
Each process sees only the clients that are connected to that specific process.
As you can see, the information you are asking for isn't available anywhere. The pub/sub implementation is distributed across different machines. Each process / machine only manages the small part of the pub/sub client list.
EDIT (1) - answering updated question
There are three possible approaches to solve this question:
a client-side solution;
a server-side solution; and
a lazy (invalidation) approach.
As a client side solution, the client could register to a global "server-notifications-channel". When a "re-authenticate" message appears, the client should re-authenticate, initiating the unique token generation on it's unique connection.
A server side solution requires the server-side connection to listen to a global "server-notifications-channel". Then the connection object will re-calculate the authentication token and transmit a unique message to the client.
The lazy-invalidation approach is to simply invalidate all tokens. Connected clients will stay connected (until they close the browser, close their machine or exit their app). Clients will have to re-authenticate when establishing a new connection.
Note (added as discussed in the comments):
The only solution that solves the "thundering herd" scenario is the lazy/invalidation solution.
Any other solution will cause a spike in network traffic and CPU consumption since all connected clients will be processing an event at a similar time.
Implementing:
With ActionCable a client-side solution might be easier to implement. It's design and documentation are very "push" oriented. They often assume a client side processing approach.
On iodine, server-side subscriptions simply require a block to be passed along to the client.subscribe method. This creates a client-specific subscription with an event that runs on the server (instead of a message sent to the client).
The lazy-invalidation approach might hurt user experience, depending on the design, since they might have to re-enter credentials.
On the other hand, lazy-invalidation might be the safest, add to the appearance of safety and ease the burden on the servers at the same time.
WARNING: Please see #Myst answer and associated comments. The answer below isn't recommended when scaling beyond a single server instance.
PATCH REQUIRED FOR DEVELOPMENT AND TEST ENVIRONMENT
module ActionCable
module SubscriptionAdapter
class SubscriberMap
def get_subscribers
#subscribers
end
end
end
end
CODE TO GET IDs OF MODELS SUBSCRIBED TO
pubsub = ActionCable.server.pubsub
if Rails.env.production?
channel_with_prefix = pubsub.send(:channel_with_prefix, ApplicationMetaChannel.channel_name)
channels = pubsub.send(:redis_connection).pubsub('channels', "#{channel_with_prefix}:*")
subscriptions = channels.map do |channel|
Base64.decode64(channel.match(/^#{Regexp.escape(channel_with_prefix)}:(.*)$/)[1])
end
else #DEVELOPMENT or Test Environment: Requires patching ActionCable::SubscriptionAdapter::SubscriberMap
subscribers = pubsub.send(:subscriber_map).get_subscribers.keys
subscriptions = []
subscribers.each do |sid|
next unless sid.split(':').size === 2
channel_name, encoded_gid = sid.split(':')
if channel_name === ApplicationMetaChannel.channel_name
subscriptions << Base64.decode64(encoded_gid)
end
end
end
# the GID URI looks like that: gid://<app-name>/<ActiveRecordName>/<id>
gid_uri_pattern = /^gid:\/\/.*\/#{Regexp.escape(SomeModel.name)}\/(\d+)$/
some_model_ids = subscriptions.map do |subscription|
subscription.match(gid_uri_pattern)
# compacting because 'subscriptions' include all subscriptions made from ApplicationMetaChannel,
# not just subscriptions to SomeModel records
end.compact.map { |match| match[1] }

Consuming Rails 5 ActionCable

I'm playing around with the new ActionCable feature. Is there a way of communicating with the ActionCable server using say socket.io or from an application using React or ReactNative?
Perhaps I'm confusing the correct use of ActionCable and it is not intended to be used as a let's say API replacement and it is meant to be used as a supporting front end technology for the same app.
Any good example or guide to use ActionCable as standalone WS server would be appreciated if this is possible.
You can interact with ActionCable as you would normally with any WebSocket libraries.
To do so, you would stream from a Channel in Rails:
class ExampleChannel < ApplicationCable::Channel
def subscribe
stream_from 'example'
end
end
Then, you may connect to the Rails WebSocket through your stand-alone client and subscribe to the message using the ActionCable protocol:
function Socket(url) {
const ws = new WebSocket(url);
ws.onopen(() => {
ws.send('{"command":"subscribe","identifier":"{\"channel\":\"ExampleChannel\"}"');
});
}
Reference:
http://edgeguides.rubyonrails.org/action_cable_overview.html#channels
https://github.com/NullVoxPopuli/action_cable_client/blob/master/README.md

Trigger the reloading of a page after a background job

I have a rails 4 application, that is doing a background calculus. So the user press a button and it launches a background job for the long calculus (using delay_job) and arrive on a waiting page. Despite the fact that the calculus is done asynchronously, I would like to find a way to warn the user and automatically reload it page once the calculus is finish.
I have looked at several solutions :
Faye or private_pub => I can't afford to use another server for this very specific job
ActionController::Live => I am not sure how to trigger the action once the job is finished
Polling => best solution for the moment, but I expect to find something that is less greedy than polling
If you have any smart solution for this problem, or any advise for my limitation in the listed ideas.
I'll explain how this works for you
HTTP
When you send the request to Rails, your browser doesn't have any way to "listen" to what happens to anything other than a direct response. The HTTP request is "sent" & doesn't have any mechanism to wait until the asynchronous process is complete
Your job is to give your app a "listener" so that your front-end can receive updates as they are generated (out of scope of HTTP), hence the websocket or SSE stuff
We've set up what you're looking for before, using Pusher
"Live"
Achieving "live" functionality is what you need
This basically means keeping a request open "perpetually" to the server; listening to any of the "events" the server sends back. This is done with JS on the front-end, where the client will "subscribe" to a channel (typically a user-centric one), and will then have all the data sent to it
We used a third-party system called Pusher to achieve this recently:
#GemFile
gem "pusher", "~> 0.12.0"
#app/controllers/message.rb
def send_message
public_key = self.user.public_key
Pusher['private-user-' + public_key].trigger('message_sent', {
message: "Message Sent"
})
end
#app/assets/javascripts/application.js
pusher = new Pusher("***********",
cluster: 'eu'
)
channel = pusher.subscribe("private-user-#{gon.user}")
channel.bind "message_sent", (data) ->
alert data.message
Hope this gives another option for you

How to push content in real time in rails 3

I'm wondering the way to push content in my site in real time..
I've researched some time and I realized that there are many ways to do this work.
Some considerations first:
I have a Rails 3 App with some social functionality..one of the features I want to achieve is to notify my users when a new activity is created.
My first approach was to implement SSE and a controller that searches in the DB for new registries after the last loaded activity.
This can work but this requires too many DB queries when all I need is to push the activity to all active users (related with that activity) when a new activity is created, without the need to do DB queries..
My second approach was to create a route pointing to a controller and an observer to catch when an new activity is created, later pass the current activity created to the controller and finally render the activity partial through SSE.
Here comes my problem.. how can connect the observer with the controller and pass it the current activity to be rendered?
And there, can I use another controller type such as a Metal Controller or even an Abstract one?
In this point I realized that could be another approach to do this job..
It'd be really nice if I could find a way to do this without having to develop it in another language or framework.. I think that should have a gem that could do real time apps easier..
What do you think?? I'm on the right way or what I should do to achieve this??
Many Thanks!!
Rails 3.0+ Push Content in Real-time with PubNub
In order to safely and securely push real-time updates to your users with some social functionality on your web site or web app there are, as you say, many options available and many nuances to consider with this as well. There is a gem for this! And I will help you get started by providing a simple start guide here for your Rails 3.0+ app and web site. First you'll want to run gem install pubnub which is the real-time component known as PubNub that will allow you to send notifications directly to targeted users or a group of users on your website and apps. This allows you to target only those who are online and interested in receiving the notifications from your Rails server. You can consider this as efficient server-sent events that require no DB Lookup or other resource intensive operations on your servers.
Ruby GEM Install
gem install pubnub
Require PubNub GEM
require 'pubnub'
Initialize The PubNub Class
pubnub = Pubnub.new(
:publish_key => 'demo', # publish_key only required if publishing.
:subscribe_key => 'demo', # required always
:secret_key => nil, # optional - message signing
:cipher_key => nil, # optional - AES 256 Crypto
:ssl => true # optional - SSL 2048bit
)
From Rails 3.0+ Send Messages to the Web App
Now from your Rails App with the pubnub class ready, you can send any kind of JSON data type including a String, Number, Array and Dictionary/Object:
a "String"
a Number 123
an array [ 1, 2, 3 ]
an object { :a => "apple" }
The following code will show you the process which will pass a message from your Rails App directly to the browser or web app that you users are running. Essentially sending the data to your users when you need to directly from your server. The pubnub.publish(...) function will serialize the message you send as a JSON for transport to your mobile and web apps.
## Broadcast or Unicast to Browser App User(s)
pubnub.publish(
:channel => "event_feed_channel",
:message => { :event => "update", :from => "Jay", :feed => "New Updates!" },
:callback => lambda { |info| puts info }
)
This process works great for performance and security considerations and never requires DB queries; saving you from hammering on your Database server and other resource intensive operations. You may consider this process as SSE (Server-Sent Events) while you are using the PubNub GEM in your Rails applications.
Using the PubNub GEM you are essentially sending events from your Server direct to your User's Browser/App. It removes the need for client-side periodic AJAX requests. This is because you are only sending data to your user's when new information is available. You may also consider using more PubNub Real-time Network features like Presence and Storage/Playback by visiting the home site to learn more details about the Ruby features. And also use the Developers Dev Console for debugging if needed during your integration into your Rails code.
Setup Browser to Receive Server-Sent Events
Next you need to setup your app to receive the Server-Sent Events by running init and passing in the
<script src=http://cdn.pubnub.com/pubnub-3.4.2.min.js ></script>
<script>(function(){
// Class Setup
var pubnub = PUBNUB.init({ subscribe_key : 'demo' });
// Event Hook Setup
pubnub.subscribe({
channel : "event_feed_channel",
message : function(m){ pubnub.events.fire( m.event, m ) }
});
// RECEIVE the "update" Server Broadcast Event in the Browser
pubnub.events.bind( "update", function(message) {
console.log( "update a <div> with received event: ", m );
} );
})();</script>
Note you may be interested in Advanced Subscribe Connectivity Options and Events.
Notice how the "update" event is identical to the :event => "update" message value in the ruby code. This means that the event will be triggered based on the name you supply and you need to bind the event to trigger code to execute on your Web App page. Inside this triggered event you will update the User Interface with any relevant details that the user would want to see on their screen in real-time.
Also notice the :channel => "event_feed_channel" is the publish channel, and this must be identical as the subscribe channel in the JavaScript client code.
If you want to read the source code of the Ruby GEM, it is available via GitHub PubNub Ruby-based APIs.
These are the basics for getting started with Ruby 3.0+ Apps and sending events directly from your server to your End-users. Note this also works for Mobile Apps that run on iOS Web Browsers or Android Browsers. Also old versions of IE are supported for this mechanism. In addition you could optionally consider server sent events from your Rails servers directly to Native iOS applications and Native Android Applications. If you have any questions ask them here on this Forum.
I've built a realtime rails gem and a sample realtime server (node.js) which enables easy realtime messaging without the need of a paid, third party, such as Pusher or PubNub. It works with Rails 3+.

Resources