Rails flash message disappear after few seconds - ruby-on-rails

I am setting my flash message in controller. But flash message is displayed only for few seconds before Vue.js render it's template and flash message disappear. My controller code looks like this:
class ResultsController < ApplicationController
def new
...
end
def create
...
tournament_match = TournamentMatch.find_by(id: tournament_match_id)
if tournament_match.score_cards.attach(params['score_cards'])
flash[:success] = 'Score card uploaded'
else
flash[:warning] = 'Score card upload failed'
end
redirect_to public_results_path
end
end
I want to display flash message bit longer. So how can I display flash message for bit longer? I tried to sleep for 5 seconds in Vue Component in beforeCreate life cycle hook but thats also not working. In Rails I tried flash.keep but that also didn't work. How can I sleep for 5 seconds in Vue.js before rendering the template ?
Update:
My view for flash message looks like below
<div class='flash'>
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>">
<%= value %>
</div>
<% end %>
</div>

So I fixed the problem by making div position: fixed. My final css looks like this
.flash {
position: fixed;
z-index: 10;
}
Vue js was taking all space in the page thats why flash message was invisible, so setting position to fixed makes div remain fixed at the top of page and I can see the flash message.

Related

rails 5.1.4 ( ActionCable + Jobs + Devise + ConversationsController.render )

Actually I need a big help, I'm using rails 5.1.4, I get stuck when rendering a partial with ActionCable and background Jobs. In my partial, I have a condition on current_user to check if current_user is the message owner and get his username with green color to make somes differents, so far display an update and delete button for each message he's the owner in the room conversations. But, if I refresh the page or remove current_user in the partial before rendering it works. However, I want that condition happens. Or, looking for a best way if there is one. THANKS IN ADVANCE.
Here is the error displayed in my terminal:
ERROR: ActionView::Template::Error: Devise could not find the warden::Proxy instance on your request environment.
Those are my files content.
The partial: _conversation.html.erb
<div id="message">
<% if conversation.user == current_user %>
<span style="text-align: center; color: green"><%= conversation.user.username %>
</span>
<% else %>
<span style="text-align: center; color: magenta"><%=
conversation.user.username %></span>
<% end %>
<p style="color: black"><%= conversation.body %></p>
</div>
Message_controller.rb
def create
conversation = #room.conversations.new(conversation_params)
conversation.user = current_user
conversation.save
MessageRelayJob.perform_later(conversation)
End
Room/show.html
<div id="conversations" data-room-id="<%= #room.id %>">
<%= render #room.conversations %>
</div>
MY JOB CLASS
class MessageRelayJob < ApplicationJob
queue_as :default
def perform(conversation)
ActionCable.server.broadcast "room_tchats_#{conversation.room.id}_channel",
conversation: render_conversation(conversation)
end
private
def render_conversation(conversation)
ConversationsController.render partial: 'conversations/conversation', locals: {conversation: conversation}
end
end
It can be happening when you try to get Devise current_user in some part of code being managed by action cable. like a background job to render comment or something else. you can resolve it by using something like following in your controller, since you cant access warden at a model or job level(according to my limited knowledge): (I call a job right after creation of my comment in CommentsController create action, which calls a private method to render a comment partial containing edit and delete button, for which current_user was required)
def create
#product = Product.find(comment_params[:product_id])
#comment = #product.comments.build(comment_params)
#comment.save!
gon.comment_id = #comment.id
gon.comment_user_id = #comment.user_id
ActionCable.server.broadcast "chat", comment: render_comment
render :create, layout: false
end
def render_comment
CommentsController.renderer.instance_variable_set(:#env, {"HTTP_HOST"=>"localhost:3000",
"HTTPS"=>"off",
"REQUEST_METHOD"=>"GET",
"SCRIPT_NAME"=>"",
"warden" => warden})
CommentsController.render(
partial: 'comments/comment_detail',
locals: {
product: #product,
comment: #comment
}
)
end
this will help you resolve warden issue, if you have used devise's current_user in that partial, it will give you the commentor user (as it should since that user initiated the rendering of partial). Now to solve this, if you have a front end framework you might need to fetch the current user from cookies in order to restrict some actions like edit/delete. but if you are working in pure rails the solution I came across is that you have to make a hidden field in the dom having current users id, and you will fetch that id for comparison in a script. you might need to access rails variables in javascript, for that you can use GON gem. I know this answer might contain much more than asked but I've searched alot and no where I found a satisfactory solution to this problem, feel free to discuss.

Rails 5 flickering of div from browser cache

Hello this is my first question to Stack Overflow. I built a social networking site with Rails 5.0.2. The initial page renders a collection of users and the application layout template includes a chat icon at the top that shows the number of unread messages in a circular red badge.
The red badge is a div styled and positioned by CSS.
Inside the red badge div I render a layouts/_notifications.html.erb
partial to populate the number of unread messages
The notifications partial calls a helper method to return the number of unread messages
The same helper method hides the div if there are no unread messages
The red badge div looks like this:
<div id="unread_conversations" style="<%= "display:none;" if unread_conversations == 0 %>">
<%= render partial: 'layouts/notifications' %>
</div>
The _notifications partial looks like this:
<%= unread_conversations %>
The helper method looks like this:
def unread_conversations
return Conversation.where("(conversations.sender_id = ? OR conversations.recipient_id = ?) AND messages.sender_id != ? AND messages.opened_at IS NULL", current_user, current_user, current_user).joins(:messages).count
end
Additional information:
On receipt of an ActionCable broadcast I re-render the red badge div asynchronously (and this works great):
received: function(data) {
// Called when there's incoming data on the websocket for this channel
value = $("#unread_conversations").html();
value = parseInt(value) + 1;
$("#unread_conversations").html(value);
$("#unread_conversations").show();
$('#sound_element')[0].play();
}
The problem:
After a message has been read...
When the user goes to another page by clicking a link, the red badge flickers on and then disappears after the page completely reloads.
And, if they return using a browser back button the red badge appears and stays on.
The question:
How do I make it so that the red badge div is hidden on all pages once the user reads all their messages?
My attempted (and failed) solutions:
1) Hide the red badge by default in the layout template and then show it using javascript.
<div id="unread_conversations" style="display:none;">
<%= render :partial => 'layouts/notifications' %>
</div>
and
<% if unread_conversations > 0 %>$('#unread_conversations').show();<% end %>
3) Tried to prevent caching of the partial.
render partial: 'layouts/notifications', cache: false
Neither fixed the problem.
Would you please try this out once i think it would work:
<% unless unread_conversations == 0 %>
<div id="unread_conversations">
<%= render partial: 'layouts/notifications' %>
</div>
<% end %>

Test that a toast fires off in a Capybara feature spec?

I have a Rails app that I've recently refactored standard flash[:success] messages to use a flash[:toast], using http://materializecss.com/dialogs.html
Here's what I'm doing with my flash partial:
<% flash.each do |type, message| %>
<% if type == "success" %>
<div class="alert alert-success alert-dismissable" role="alert">
...
</div>
<% elsif type == "toast" %>
<script>
$(function() {
Materialize.toast('<%= message %>', 3000);
});
</script>
<% else %>
<div class="alert alert-danger alert-dismissible" role="alert">
...
</div>
<% end %>
<% end %>
This works and looks awesome, especially on mobile and for complex data pages where I'm sending the user back to the middle of the page, and a standard flash success message on the top of the page would not be visible.
I can easily test if flash[:toast] is not nil in a controller test, but in capybara feature tests, I don't have access to that, and I can't use the standard code that I used to use to test flash[:success] like:
expect(page).to have_content("Your email address has been updated successfully.")
expect(page).to have_css(".alert-success")
Right now I am resorting to just testing that an alert danger is not present, like:
expect(page).to_not have_css(".alert-danger")
This works, but isn't really testing that the toast fired off. Is there any way to check that the javascript fired or that the toast appeared on the page for the 3 seconds?
As long as you're using a JS capable driver (pretty much anything but the default rack-test driver) then
expect(page).to have_content("The message shown by the toast")
should find the toast when it displays on the page. If you want to check that it appears and disappears within 3 seconds you could do something like
expect(page).to have_content("The message shown by the toast")
expect(page).not_to have_content("The message shown by the toast", wait: 3)
The first statement should wait for the text to appear and the second will wait up to 3 seconds for it to disappear. If you want to actually verify the text is being shown in the toast container then you could do
expect(page).to have_css("#toast-container", text: "The message shown") #update css selector to whatever container you are displaying the toast in
expect(page).not_to have_css("#toast-container", text: "The message shown", wait: 3)

Rails clear flash notice after JSON response

I am using Angular to send JSON request to the controller, in my controller I flash notice like this:
flash[:notice] = "Toon has been tagged"
After I want to use my rabl template to return JSON response and also include my flash notice content like this:
object #toon
attributes :id, :uuid, :get_tag_string
if flash
node(:flash) do |f|
flash.each do |k, v|
{ :msg => v, :name => k }
end
end
end
attributes :errors
My angular code handles the response and display the flash notice contents correctly. But here comes the problem, when the page refreshed the flash message is displayed again because of following code in my layout view:
<% flash.each do |key, value| %>
<div class="row-fluid">
<div class="span8 offset1"><%= content_tag(:div, value, class: "alert alert-#{key} center")%></div>
</div>
<% end %>
I could remove this or do an after_filter in my controller to call flash.clear
IS THERE A BETTER WAY TO DO THIS?
THANKS!!
I too am using angularjs with rails. The way I am handling error messages from the server (rails) is to use angular route changing events. Since you are dealing with flash messages from the server, it's really the same concept.
In my angular app where I display errors (in your case flash messages) I use an ng-show with a variable, e.g.,
<div ng-show="showMessages" class="alert">
{{myMessages}} // obviously here you may have an ng-repeat or similar
</div>
In my main angular controller I am setting $scope.showMessages (either true or false). At this point it's the same issue. In my controller I use the following callback to see when the route has changed. When it's changed I can set $scope.showMessages to false.
$scope.$on('$routeChangeStart', function (scope, next, current) {
$scope.showMessages = false;
}
I also use this routeChangeStart event to deal with times where I want the message to come out on the next page- by adding another var to control the "show this on the next page only".
I personally wouldn't go back to the server to clear the flash messages - that seems "expensive" whereas you wouldn't be forced to make that extra round trip.
Solve with flash.now in my controller :)
Thanks for your answer Arthur Frankel, liked your way to hand the messages

Disabling Flash message without disabling cache on click on back button In RAILS

I have an application written in rails. I have flash messages displaying the user saying he logged in successfully or created or updated his profile. Once the user logs in a flash message("Welcome") is displayed and the user navigates to a different page. If the user clicks BACK button the flash message ("Welcome") is displayed again.
I dont want to disable Cache as it will affect the performance.
Any ideas to tackle this situation will be helpfull.
Thanks.
This is for people coming to this question via search engines.
Problem
When the user clicks on the back button, a fresh request is not made to the server, rather the browser's cached copy is displayed. However, during the first request, the browser has already cached the flash message, and on clicking the back button, the flash message gets displayed again.
Solution
A way to get around this is to use the browser's localStorage or sessionStorage.
Change your code so that the flash messages are shown or hidden via javascript. When the flash message is shown, store that fact in the browser's sessionStorage.
Next time, check if the flash message has already been shown and if so, do not show it again.
I prefer to use sessionStorage instead of localStorage as sessionStorage gets cleaned up on closing the browser. It is helpful if you have too many flash messages. Otherwise, you will have to write the logic for removing older entries
# app/views/layouts/application.html.erb - or wherever your flash messages reside
...
<%
flash_name_mappings = {
"notice" => "success",
"error" => "danger",
"alert" => "warning"
}
%>
<% flash.each do |name, msg| %>
<% if msg.is_a?(String) %>
<% flash_token = Time.current.to_s(:number) # or use any other random token generator, like Devise.friendly_token %>
<div id="flash-<%= flash_token %>" style="display:none" class="alert alert-<%= flash_name_mappings[name] || name %>">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<%= content_tag :div, msg.html_safe, id: "flash_#{name}" %>
</div>
<script type="text/javascript">
$(document).ready(function(){
if (typeof(Storage) !== "undefined") {
// Code for localStorage/sessionStorage.
if(sessionStorage["flash-<%= flash_token %>"] != "shown"){
$("#flash-<%= flash_token %>").show();
sessionStorage["flash-<%= flash_token %>"] = "shown";
}
} else {
// Sorry! No Web Storage support..
$("#flash-<%= flash_token %>").show();
}
}
</script>
<% end %>
<% end %>
...
Try to use Flash.now instead:
flash.now[:notice] = "Welcome"
In comparison to flash method it displays the message only until the next action is performed. Normal flash message is displayed until the next request is send to the server. I hope it helps.

Resources