In a Rails app I've used Faye (Rack adapter) for push notifications (for chat).
I want to use Faye for another use case (more push notifications) but I can't seem to figure it out.
In my app a model can be created from a background job so I want to refresh one of my views (say the index action) when the model gets created.
Like so:
app/models/post.rb
class Post
include Mongoid::Document
after_create :notify_subscribers
private
def notify_subscribers
Faye::Client.publish("/posts")
end
end
app/views/posts/index.html.erb
<%= subscribe_to(posts_url) do %>
uhh what do I do here? ajax call to refresh the whole page??
<% end %>
So is publishing the notification directly from an after_create callback a good idea
and when I get a message from the Faye server how to I go about implementing the "update"?
Do I just do an AJAX call to reload the data from the server? That seems like it would be slow.
Further I want to use something similar for updates to the model (say a user added some comments or the author changed the content) so thrashing the DB all the time doesn't seem like a good plan...
First of all, yes, publishing the notification with after_create is fine. What you should do in your notify_subscribers method is to publish all relevant information about the new post that you want to use in the client so that you don't have to make another unnecessary AJAX request when the notification is received.
So, for instance, if the title and content of the post are relevant for the user to see immediately after it gets created, you would do something like:
def notify_subscribers
client = Faye::Client.new('http://localhost:3000/faye')
client.publish("/posts/new", {
'title' => self.title,
'content' => self.content
})
end
...and then, in the view, you would probably use jQuery to use the data about the new post which you receive from the server. Rails code won't help you here. E.g. you would do this (assuming you're using jQuery and have included faye.js in your header):
<script type="text/javascript">
$(function() {
var client = new Faye.Client('http://localhost:3000/faye');
client.subscribe("/posts/new", function(data) {
/* do whatever you need with data */
});
});
</script>
Finally, if somehow the information you want to transfer is too complex to transfer as part of the notification, or you already have existing AJAX processes for updating your view, you could just publish the post's ID and trigger an AJAX call with jQuery in the subscribe callback function. I'd recommend this only if necessary though.
Related
I am trying to add the User Appearances example (from the Rails' Guide : https://guides.rubyonrails.org/action_cable_overview.html#example-1-user-appearances ) in my app but I don't understand this part :
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
def subscribed
current_user.appear
end
def unsubscribed
current_user.disappear
end
def appear(data)
current_user.appear(on: data['appearing_on'])
end
def away
current_user.away
end
end
If someone has an explanation for the following sentence : "That appear/disappear API could be backed by Redis, a database, or whatever else." (Just above this part of code in the Rails' Guide).
I try several options, as adding a method "appear" in my model User which change on "true" a database value from my model User, but the subscribed definition call current_user.appear and then the appear definition call current_user.appear(with_param) generates a conflict ...
There is probably something I don't understand but I don't see exactly what is it ...
Thank you very much for your answers.
The sentence about "appear/disappear API backing" - means that ActionCable does not care where and how you are storing and handling users statuses - you may store only a flag or more data in database (like last seen chatroom, last seen time etc.), you may store similar data in redis or any other place you like.
(un)subscribed methods are caller by ActionCable itself upon user (dis)connection to that channel(usually this happens on page load and after navigating away/closing - and while page is open in browser it does not necessary mean that user is actually near their device), while appear/away are actions that are called from clientside js via calling perform("action_name_here") on the channel.
Example assumes that clientside code will detect user presence and send updates.
My app has some heavy callback validations when I create a new customer. Basically I check multiple APIs to see if there's a match before creating a new customer record. I don't want this to happen after create, because I'd rather not save the record in the first place if there aren't any matches.
I have a webhook setup that creates a new customer. The problem is that, because my customer validations take so long, the webhook continues to fire because it doesn't get the immediate response.
Here's my Customer model:
validates :shopify_id, uniqueness: true, if: 'shopify_id.present?'
before_validation :get_external_data, :on => :create
def get_external_data
## heavy API calls that I don't want to perform multiple times
end
My hook:
customer = shop.customers.new(:first_name => first_name, :last_name => last_name, :email => email, :shopify_url => shopify_url, :shopify_id => id)
customer.save
head :ok
customer.save is taking about 20 seconds.
To clarify, here's the issue:
Webhook is fired
Heavy API Calls are made
Second Webhook is fired (API calls still being made from first webhook). Runs Heavy API Calls
Third Webhook is fired
This happens until finally the first record is saved so that I can now check to make sure shopify_id is unique
Is there a way around this? How can I defensively program to make sure no duplicate records start to get processed?
What an interesting question, thank you.
Asynchronicity
The main issue here is the dependency on external web hooks.
The latency required to test these will not only impact your save times, but also prevent your server from handling other requests (unless you're using some sort of multi processing).
It's generally not a good idea to have your flow dependent on more than one external resource. In this case, it's legit.
The only real suggestion I have is to make it an asynchronous flow...
--
Asynchronous vs synchronous execution, what does it really mean?
When you execute something synchronously, you wait for it to finish
before moving on to another task. When you execute something
asynchronously, you can move on to another task before it finishes.
In JS, the most famous example of making something asynchronous is to use an Ajax callback... IE sending a request through Ajax, using some sort of "waiting" process to keep user updated, then returning the response.
I would propose implementing this for the front-end. The back-end would have to ensure the server's hands are not tied whilst processing the external API calls. This would either have to be done using some other part of the system (not requiring the use of the web server process), or separating the functionality into some other format.
Ajax
I would most definitely use Ajax on the front-end, or another asynchronous technology (web sockets?).
Either way, when a user creates an account, I would create a "pending" screen. Using ajax is the simplest example of this; however, it is massively limited in scope (IE if the user refreshes the page, he's lost his connection).
Maybe someone could suggest a way to regain state in an asynchronous system?
You could handle it with Ajax callbacks:
#app/views/users/new.html.erb
<%= form_for #user, remote: true do |f| %>
<%= f.text_field ... %>
<%= f.submit %>
<% end %>
#app/assets/javascripts/application.js
$(document).on("ajax:beforeSend", "#new_user", function(xhr, settings){
//start "pending" screen
}).on("ajax:send", "#new_user", function(xhr){
// keep user updated somehow
}).on("ajax:success", "#new_user", function(event, data, status, xhr){
// Remove "pending" screen, show response
});
This will give you a front-end flow which does not jam up the server. IE you can still do "stuff" on the page whilst the request is processing.
--
Queueing
The second part of this will be to do with how your server processes the request.
Specifically, how it deals with the API requests, as they are what are going to be causing the delay.
The only way I can think of at present will be to queue up requests, and have a separate process go through them. The main benefit here being that it will make your Rails app's request asynchronous, instead of having to wait around for the responses to come.
You could use a gem such as Resque to queue the requests (it uses Redis), allowing you to send the request to the Resque queue & capture its response. This response will then form your response to your ajax request.
You'd probably have to set up a temporary user before doing this:
#app/models/user.rb
class User < ActiveRecord::Base
after_create :check_shopify_id
private
def check_shopify_id
#send to resque/redis
end
end
Of course, this is a very high level suggestion. Hopefully it gives you some better perspective.
This is a tricky issue since your customer creation is dependant on an expensive validation. I see a few ways you can mitigate this, but it will be a "lesser of evils" type decision:
Can you pre-call/pre-load the customer list? If so you can cache the list of customers and validate against that instead of querying on each create. This would require a cron job to keep a list of customers updated.
Create the customer and then perform the customer check as a "validation" step. As in, set a validated flag on the customer and then run the check once in a background task. If the customer exists, merge with the existing customer; if not, mark the customer as valid.
Either choice will require work arounds to avoid the expensive calls.
Let's take a scenario:
counter 10 seconds
User visited show.html.erb page
show.html.erb fetch the value from database using <%= #post.value %>.
Counter started and each iteration of counter is for 10 seconds.
After every 10 seconds I wanted to change the #post.value using utility class.
update the #post.value in database.
Refresh the show.html.erb automatically and <%= #post.value %> will show the updated value
Above process will be run in loop until user move to other pages.
If I have to simplify the problem in code then it would like this:
View Page
<%= #post.value %>
<%= #post.name %>
Controller Method
def show
#post = Post.find(params[:id])
end
def update
.... #It's empty right now
end
def fetching_value_from_PostUpdate(current_value)
.. # Now I need to update the value in database
end
Utility class
I want to update the post.value on the basis of method written in this class. Pseudo code:
class PostUpdate
##instance_variable
def initialize(post_value)
##instance_variable = Marshal.load(Marshal.dump(post_value))
end
#Method required to calculate the value
def update_data_in_database
...
return data
end
Questions
Where do I have to put the counter? client side or server side? I don't want to use background jobs in rails.
What changes do I need to make in show method so that after every interval page will refresh automatically and pass the updated value in #post.value?
Thanks for your help!
I would go with Firebase as opposed to polling the server.
However, if you're wanting to poll the server periodically, I would just have a JavaScript method which executes every 10 seconds, or at whatever interval you'd like, and fetches whatever data you'd like asynchronously and subsequently updates the DOM.
Also, ruby wrapper for firebase api, in case that may be of interest
I would say the easiest approach doing it would be using ActionController::Live. Using it you'll be able to send SSE(Server Sent Event) to your client while having js script there to catch and update your <%= #post.value %>. Here's a pretty nice manual on using it.
Still from my point of view the most appropriate way to implement things you want to do will be using something like Faye. It is a publish/subscribe messaging system which will allow you to update your client with new data as soon as it appears, e.g., you can set an after_save in your model and publish an update to all subscribed clients. And of course there is a gem also called faye to wrap its usage.
I'm new to rails and jquery/css and webapps in general. I need guidance in building progress bar functionality. My rails app basically inserts file data into an elasticsearch engine. The data is a user's uploaded csv/excel file.
From my controller, what is the best/cleanest way to get progress-bar type percentage from the controller into a coffeescript or jquery code. I'm clueless about how status-percentage type data from the server can be rendered in the view. Below I have the controller that is of relevance. The #upload page has a button that triggers the import action. The import action renders index action once the data is loaded into elasticsearch.
The FileProcessorService is just a ruby class that does the parsing of the file and inserting each record into elasticsearch and returns data.
Here is my controller:
class FileProcessorController < ApplicationController
def index
end
def import
initialize_processor(params[:file])
if (#file_sample != nil || #index_name != nil) then
render 'index'
end
end
def upload
end
def initialize_processor(file_in)
File.open(Rails.root.join('public', 'uploads', file_in.original_filename), 'wb') do |file|
file.write(file_in.read)
end
#file_processor = FileProcessorService.new(file_in)
#file_sample = #file_processor.present_data_sample()
#index_name = #file_processor.load_index()
end
end
Since you mention you're "clueless" about how to approach this, I'll give you some ideas:
Progress
To handle a "progress bar", you're going to need away to receive regular updates at intervals. I don't know if FileProcessorService will do this - but your controller will need to send updates to your JS front-end somehow
Even if you don't have a percentage-based update from your controller, you'll want some event triggers to send updates to your system
Asynchronous
What you're dealing with is called an "asynchronous" request. This is a request outside the normal scope of HTTP requests, whereby your browser will initiate technology such as Javascript to send a request on your behalf
This basically means no refresh for the browser
You'll have to send an asynchronous request via JS, and then listen for the response. The response will be what determines your progress bar status
Pub/Sub
Asynchronous functionality gives you two "methods" to send/receive data - ajax (single request) or pub/sub (multiple requests). Pub/sub is basically how every chat application sends data -- each user gets their own "channel" and the server sends updates to it
I would recommend using a Pub/Sub service called Pusher to achieve the "live" data updates, which you can tie to the progress bar's status
Code
I've not done this before, but this is what you'd need:
You'll need to send events from your controller to a pub/sub channel (Pusher highly recommended)
The user's browser will "listen" to the updates through Pusher - allowing you to assign progress bar status each time an update is
posted
I am trying to implement a simple solution to help with some behavior. Basically I want to create a listener on a particular url, like
localhost:3000/listen
I have a callback with a 3rd party service that is sending JSON as a post request. In my rails routes I have the route setup, to accept a post request to that namespace.
The thing that I want to happen, is for some logic to be run anytime a new post comes in and to run that logic async without any disruption to the normal web service. For example, the post request will contain some data, if the data has a boolean "true", we need to fire off a Rails Mailer. I normally could do this with a simple rails controller action but this is not correct.
Any thoughts on the best approach to handle this? Would this best with a gem like eventmachine? If anyone could give their feedback to implement a simple solution that would be great!
I would look at your background jobs. I am a Sidekiq fan, and popular alternatives are Resque and DelayedJob.
Your controller will receive the response, and schedule it to be performed in the background. That will send out the mail (or whatever you need it to do) asynchronously.
class CallbackController < ApplicationController
def listen_third_party
data = params.slice([:params, :you, :care, :about])
if data[:boolean_field] == true
CallbackMailer.perform_async(data)
end
end
end