Turbo Stream Rails does not update page - ruby-on-rails

I am new to Ruby on Rails and need help with figuring out how Turbo Stream works in Rails.
I did everything by tutorials, however, the content of the page was not updating. I decided to test how Turbo Stream will behave on simple div.
In index.html.erb file I created such div with id="test":
<div id = "test">Hallow</div>
In the same file I am creating a button that calls method refresh in controller.
<%=button_to " ", refresh_path(:task => task, format: :turbo_stream)%>
The method is called and works properly, I have already checked it without Turbo Stream.
The content of refresh.turbo_stream.erb:
<%= turbo_stream.replace 'test', partial: 'refresh' %>
The content of _refresh.html.erb
<h1>GoodBye</h1>
Here is the proof that Turbo Stream works
Indeed Turbo Stream renders the page, but I get such an output.
I tried writing respond in controller, deleting turbo_stream format (it caused another error), playing with routes, but did not come up to anything.
I am sorry if this question might have been answered already, but I tried different options and nothing worked for me. Please help!

Turbo doesn't process links with formats other than html (apparently php as well).
So when you click a link or a button, that has ".json", ".xml", or ".turbo_stream" or anything else, Turbo gives up and just sends you directly to that page where browser displays it the best it can, and it knows nothing about what to do with turbo_stream, which has Content-Type: text/vnd.turbo-stream.html, so, plain text in the browser.
To fix it, remove :format:
<%= button_to "refresh", refresh_path(task: task) %>
With this button, Turbo will send a correct request, where it will wait for html or turbo_stream response, which you can see in the request header:
Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml
If the server then responds with turbo_stream format: Content-Type: text/vnd.turbo-stream.html then stream instructions will execute on the current page. There are other rules, but one step at a time.
To render an appropriate response use respond_to method in your controller action:
respond_to do |format|
format.html { render :page } # renders `page.html.erb`
format.turbo_stream { render :page } # renders `page.turbo_stream.erb`
# or render <turbo-stream> inline
# format.turbo_stream { render turbo_stream: turbo_stream.replace :test, partial: "test_partial" }
end

Related

Add a "Thank you for your order" page that is not directly visitable

When the user purchases an item we want to display a thank you page.
Then again we want this page not to be directly visitable.
In other words, if the page was to be /orders/thankyou, the user could directly navigate to it which would be rather ugly and fail aas it would have nothing to show.
how can we show this page only when in context and forbid direct navigation to it?
You can create a partial form and append it to the DOM after the purchase event is fired. In the partial view, be sure to add a redirect action if it was accessed without the purchase event firing.
For just displaying short text (and not also e.g. the order data) you could just use a flash notice. For example:
redirect_to #page, notice: "Thank you for your order!"
Ajax
Sounds like you'll be best using ajax:
#app/views/cart/index.html.erb
<%= form_tag cart_order_path, remote: true do |f| %>
... (cart form)
<%= f.submit %>
<% end %>
This (obviously very simple) form will send a "remote" (Ajax) form submission to your controller. The reason why this is important is because you will then handle the response directly in that page you just sent the request from:
#app/assets/javascripts/application.js
$(document).on("ajax:success", "#your_form_id", function(status, data, xhr) {
$("your_page_div").html(data);
});
The trick here will be to render your thank you view without a layout -
#app/controllers/cart_controller.rb
class CartController < ApplicationController
respond_to :js, only: :create
def create
... business logic here
render "thank_you", layout: false
end
end
This will render the view you want without any of the supporting "layout" HTML - giving you the chance to append that to your current view. This means that if you wanted to show the "Thank You" view without letting the user browse to it directly - that's what you'll do
How It Works
Ajax (Asynchronous Javascript and XML) is a javascript technology which basically allows you to send "pseudo requests" to your Rails backend:
Basically the same as a standard HTTP request, except handled with Javascript, Ajax gives you the ability to create the appearance of "no refresh" functionality in your app. It does this by sending requests on your behalf, through Javascript.
Ajax is typically used for small pieces of functionality on web interfaces - whereby you'll have the ability to send simple requests to the server, gaining a comparable response that you can then work into the DOM.
This is the functionality I have been proposing - whereby you'll be able to send a request to your controller (albeit using the Rails UJS engine), to which you'll then receive a response. This response can then be worked into your page, thus providing you with the ability to show the "Thank You" page without the user refreshing.
You can use any solution from the following:
Using ActionDispatch::Flash:
flash[:notice] = "Thank you for your order!"
redirect_to #page
or
redirect_to #page, notice: "Thank you for your order!"
Using alert, in show.js.haml file (assuming you use action show in orders_controller.rb):
alert("Thank you for your order!");
and then add respond_to :js, only: :show, and format.js in action show for orders_controller.rb

Using Private-pub with channels that have segment keys and coffeescript variant

I'm trying to replicate a push notification system similar to facebook's using private_pub. Ideally I would want to link this to show notifications using a gem such as gritter (other gem suggestions are welcome)
Whenever a certain action from a controller is called, I want to send a notification to all subscribers that are part of a specific id. As long you are logged in, you are subscribed to the channel, achieved by putting the subscribe_to in the layouts.
in the view:
<%= subscribe_to "/messages/#{#group_id}" %>
in the controller
PrivatePub.publish_to("/messages/#{#group_id}", "alert('test')")
this works just fine, however I would like to have something more sophisticated than an alert as a response (such as a gritter notification), so instead:
PrivatePub.publish_to("/messages/#{#group_id}", data: #some_data)
Following the tutorial, they use coffeescript for this. However, I cannot get the simple alert going (probably due to the id in the channel)
In this question, the OP was able to solve this using a js.erb view. But I can't get it to work.
disclaimer: my js and coffeescript knowledge is almost zero.
Any help is appreciated :)
EDIT
Some more info: I've a method in a controller that's part of a public API, and expects POST request. If everything is ok it sends out a JSON success response. Aside from this, the same method sends a notification to all users of a specific group.
I've actually managed to get this working, putting this in the controller:
callback method:
respond_to do |format|
format.js #-> calls callback.js.erb
#format.json { render json: {"success" => true}.to_json }
end
and putting the gritter stuff in my_api_controller/callback.js.erb:
<% publish_to "/messages/#{#group_id}" do %>
<%= add_gritter(
"Nova " + link_to("reserva", reservation_path(#r)) + " de #{#channel} para " +
link_to(#hostel_name, hostel_path(#hostel_id)),
:title => "Nova reserva!",
:sticky => true,
:image => :notice
) %>
<% end %>
note: since the subscription to the channel is done in every view (through the layout), you can receive a notification on any page/view
My problem at the momento is, as you can guess, the JSON response. Since I cant render two responses, only the js.erb is called, but the JSON response is never sent
Although I've not got much experience with this gem, here's something which may help:
JS
Client-side, your JS is basically running an eventlistener on the private_pub object (defined when you include the private_pub / gritter JS on your page), which you can use to perform other actions (call alerts, append data to page, etc)
It seems your back-end is working, it's just the receipt & processing of the data from the server you're struggling with. To remedy this, you can do 2 things: 1) run a standard JS call from application.js or run a js file from your controller action:
Controller
According to the private_pub documentation, you should do this to create a JS file:
#app/controllers/your_controller.rb
def create
#message = "Hello"
respond_to do |format|
format.html { PrivatePub.publish_to("/messages/#{#group_id}", "alert('test')") }
format.js #-> calls create.js.erb
end
end
#app/views/your_controller/create.js.erb
<% publish_to "/messages/new" do %>
$("#chat").append("<%= j render(#messages) %>");
<% end %>
Browser
#app/assets/javascripts/application.js.coffee
PrivatePub.subscribe("/messages/new", (data, channel) ->
alert data.message.content
I was able to accomplish this by directly adding the gritter script in the publish_to method of Privat pub.
In my controller:
PrivatePub.publish_to
"/some/URI/#{entity.id}"
,"jQuery.gritter.add({
image: '#{ActionController::Base.helpers.asset_path('notice.png')}'
, sticky: true
,title:'#{t('some_title')}'
, text: '#{t('some text'}'
});"
render json: {"error"=>{"code"=>20,"msg"=>e.message},"success" => false}.to_json
Basically, I was able to publish to PrivatePub witouth resorting to the html response, wich enabled me to return a JSON response as intended.
I guess, you can handle your problem with the help of gon gem like below:
In view
<%= subscribe_to "/messages/#{#group_id}" %>
In controller
gon.group_id = #group_id
PrivatePub.publish_to("/messages/#{#group_id}", message: #message)
In messages.coffee
if gon.group_id
PrivatePub.subscribe "/messages/#{gon.group_id}", (data, channel) ->
jQuery.gritter.add
image: '/assets/notice.png'
title: 'Notification!'
text: data.message.content
But, gon.group_id can make trouble sometimes so you need to take care of that.
So, I recommend to use js.erb which is easy and we can access to controller's variable easily in js.erb file.
Hope that answer your problem.

Respond with javascript to an HTML request?

Is it possible to respond to an HTML request with javascript? By this I mean that I don't want the page to be refreshed and plus execute some javascript code. I wonder if this is possible by changing only the server side?
In my action, I have something selected to respond to js and html like this:
respond_to do |format|
format.js
format.html
end
And I have a .js.erb file that, of course, should be rendered when the browser requests a javascript.
Can this be done? How?
You cannot do this by only changing the server side code. If you'd like javascript to be executed upon the server's response, then the form will need to be submitted through javascript. This is because javascript will eval the response from the server and run the code.
This can somewhat be trivially added in Rails:
<%= form_for #user, remote: true %>
When it is submitted, then you can send back javascript. For example, to alert that a user was added:
/app/views/users/create.js.erb
alert("user <%= j #user.email %> was added");

Rails link_to with remote: true processing html instead of js after page refresh

I have a search page in my app, where there is an ajax search form. The search form works properly, passing parameters to the model to filter the search, and the model returning a collection of results. Upon search submit, #results are rendered on the page. Each #result then has a link to an action on it, like:
<%=link_to "Message", message_user_path(:id => user.id), :remote => true%>
Where this action in the controller is:
respond_to :js, :html
def message
#user_id = params[:id]
#user = User.find_by_id(#user_id)
respond_to do |format|
format.html
format.js
end
end
and this responds with message.js.erb, which triggers a messaging panel to pop up with a message to the user. All of this is working correctly, checking the log I see the correct get request sent, and the correct format being processed:
Started GET "/users/3/message"
Processing by UsersController#message as JS
However, if I refresh the page and try to click the same link that was working before, I get the error Template is Missing. Checking the log, I see that now there are two requests sent, first an html then the same js request.
Started GET "/users/4/message"
Processing by StudentsController#message as HTML
...
Completed 406 Not Acceptable in 3ms (ActiveRecord: 1.0ms)
Started GET "/users/4/message"
Processing by StudentsController#message as JS
The html request throws the missing template error. Does anyone know why refreshing the page causes rails to attempt to respond with an html request to a remote link?
EDIT: routes.rb
resources :students do
member do
get 'message'
end
end
Does your app/assets/javascripts/application.js contain
//= require jquery
//= require jquery_ujs
?
And your erb contains <%= javascript_include_tag "application" %>?
I was just struggling with a problem like this for HOURS and the last of those two points fixed it; I saw the first point mentioned in some other questions so I'll repeat it here.
Hope that helps.
(Credit where credit's due)
What solver it for me was adding :format => "js"
So in your case:
<%=link_to "Message", message_user_path(:id => user.id, :format => "js"), :remote => true %>
In general, when you use link_to on a particular button or so, when you press the button, as js request is send to the controller, but also searches for the respective .js.erb file and so on.
My solution was to replace
format.json do
with
format.js do
you can troubleshoot the request by setting a breakpoint (i use pry) in the controller and then look at the variable
request.format
For newer versions of Rails, this should be fixed where using remote: true within the link_to code, as the original poster was doing, will only look for a .js format to respond with. As others have said, if you never need an html response, then you can remove that from your code all together; you won't even need a respond_to, respond_with, etc as Rails will auto respond with JS looking for the template you already have made. So your controller code would look like this:
def message
#user_id = params[:id]
#user = User.find_by_id(#user_id)
end
And the link would still be this:
<%=link_to "Message", message_user_path(:id => user.id), :remote => true %>
Or this would work as well (my preferred way of syntax):
<%=link_to "Message", message_user_path(id: user.id), remote: true %>
This code will call the controller action which will look for the template message.js.erb.
I know this question is old now, but for anyone looking for answers and using current Rails 6+ (I'm using 7.0.0alpha), and if you are getting this same type of issue where both HTML and JS templates are being requested; check that turbolinks is not what is causing the issue. Sometimes turbolinks can cause a request to be sent twice and it may be sending the first request as an HTML request.
My form with remote: true was inside another form and I didn't know it.
So make sure it isn't inside another form.

Rails: What does it actually mean to "render a template"

I've become a bit confused about the idea of "rendering" a "template" due to the way an author speaks about it in a book I'm reading.
My original understanding of "rendering a template" was that it meant that Rails is providing the content that is viewed on the screen/presented to the viewer (in the way that a partial is rendered) but the book I'm reading seems to be using the concept of "rendering a template" to also mean something else. Let me explain in context
This book (rails 3 in action) sets up a page layout using the conventional layouts/application.html.erb file, and then it "yields" to different view pages, such as views/tickets/show.html.erb which adds more content to the screen. that's all straightforward..
Within this view views/tickets/show.html.erb, there is a rendering of a partial (which is also a straightforward concept).
<div id='tags'><%= render #ticket.tags %></div>
Now within this partial there is, using ajax, a call to a "remove" method in the "tags_controller.rb" which is designed to allow authorized users to remove a "tag" from a "ticket" in our mock project management application.
<% if can?(:tag, #ticket.project) || current_user.admin? %>
<%= link_to "x", remove_ticket_tag_path(#ticket, tag),
:remote => true,
:method => :delete,
:html => { :id => "delete-#{tag.name.parameterize}" } %>
<% end %>
Now here is the "remove" action in the tags controller (which disassociates the tag from the ticket in the database)...
def remove
#ticket = Ticket.find(params[:ticket_id])
if can?(:tag, #ticket.project) || current_user.admin?
#tag = Tag.find(params[:id])
#ticket.tags -= [#tag]
#ticket.save
end
end
end
At the end of this remove action, the author originally included render :nothing => true , but then he revised the action because, as he says, "you’re going to get it to render a template." Here's where I get confused
The template that he gets this action to render is "remove.js.erb", which only has one line of jquery inside it, whose purpose is to remove the "tag" from the page (i.e. the tag that the user sees on the screen) now that it has been disassociated from the ticket in the database.
$('#tag-<%= #tag.name.parameterize %>').remove();
When I read "rendering a template" I expect the application to be inserting content into the page, but the template rendered by the "remove" action in the controller only calls a jquery function that removes one element from the page.
If a "template" is "rendered", I'm expecting another template to be removed (in order to make room for the new template), or I'm expecting content to be "rendered" in the way that a partial is rendered. Can you clarify what is actually happening when a "template" is "rendered" in the situation with the jquery in this question? Is it actually putting a new page in front of the user (I expected some sort of physical page to be rendered)
You're nearly there! Rendering a template is indeed always about producing content, but for a slightly wider description of content. It could be a chunk of html, for example an ajax call to get new items might produce some html describing the new items, but it doesn't have to be.
A template might produce javascript as it does in your second example. Personally I am trying to avoid this and instead pass JSON back to the client and let the client side js perform the required work.
Another type of rendering you might perform is to produce some JSON. APIs will often do this, but you might also do this on a normal page. For example rather than rendering some javascript to delete tag x you might render the json
{ to_delete: "tag-123"}
and then have your jQuery success callback use that payload to know which element to remove from the DOM, by having this in your application.js file
$('a.delete_tag').live('ajax:success', function(data){
var selector = '#' + data.to_delete;
$(selector).remove()
}
(Assuming that your delete links had the class 'delete_tag')
Rendering JSON like this isn't really a template at all, since you'd usually do this via
render :json => {:to_delete => "tag-#{#tag.name.parameterize}"}
although I suppose you could use an erb template for this (I can't imagine why though).
My understanding is that js.erb is "rendered" by executing the javascript functions within it. Very often something like the below is done:
jQuery(document).ready(function() {
jQuery('#element').html('<%= escape_javascript(render pages/content) %>');
});
There's a really succinct overview of rendering at http://guides.rubyonrails.org/layouts_and_rendering.html that may help as it also goes into the details of the ActionController::Base#render method and what happens behind the scenes when you use render :nothing (for example). Render but can be used for files or inline code as well -- not just 'templates' in the traditional sense.

Resources