Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turbo new install - forms do not redirect #122

Open
kiddrew opened this issue Feb 10, 2021 · 76 comments
Open

Turbo new install - forms do not redirect #122

kiddrew opened this issue Feb 10, 2021 · 76 comments
Assignees

Comments

@kiddrew
Copy link

kiddrew commented Feb 10, 2021

I just installed Turbo in an existing Rails 6 app and all my existing forms are broken. They submit as expected but the redirect after doesn't happen. I'm able to interact with Turbo as expected - ie, I have a frame loading correctly, so it appears I loaded Turbo correctly in Webpack.

import "@hotwired/turbo-rails"

And with just a very simple form (using slim and simple_form):

= simple_form_for @comment do |f|
  = f.input :text, label: "Comment"
  = f.button :submit

My controller performs the redirect (using responders):

class CommentsController < ApplicationController
  load_and_authorize_resource

  def index
    @comment = Comment.new
  end

  def create
    @comment.save
    respond_with @comment, location: comments_path
  end

  private

  def resource_params
    params.require(:comment).permit(
      :text
    )
  end
end

The comment gets created and the request for the redirect URL happens but the redirect does not. I have to refresh to see the changes.

@seanpdoyle
Copy link
Contributor

Thank you for reporting this.

Does your application run RailsUJS? Are your forms created with data-remote="true"?

@kiddrew
Copy link
Author

kiddrew commented Feb 10, 2021

I'm not using data-remote. I did have UJS enabled, but I disabled it to test and that didn't help either.

@MohamedTaha123
Copy link

same issue here

@SleeplessByte
Copy link

I had to explicitly use render ..., status: :unprocessible_entity to make the page show with errors and redirect ..., status: :see_other to make the page redirect.

Perhaps that helps?

@Petercopter
Copy link

@SleeplessByte That is correct. It's not well documented yet, but Rails is switching to returning a 422 on fail: rails/rails#41026

Devise will be ready for this change soon: heartcombo/devise#5340

Really excited to start playing with these new toys 🎉

@dixpac
Copy link
Contributor

dixpac commented Feb 21, 2021

@seanpdoyle there is a small issue with redirects.
When using Rails UJS, and setting the form as local: false, data-turbo="false" redirects are not working.

respond_to do |format|
  format.html { redirect ...} <---- This will not redirect. Request is a js.erb, worked on turbolinks
end

@seanpdoyle
Copy link
Contributor

@dixpac If RailsUJS is present and local: false is passed to form_with, the resulting form will have [data-remote="true"], and will be intercepted by RailsUJS ahead of Turbo, and will be submitted as a JS request instead of an HTML request.

Am I understanding your question properly?

@kiddrew
Copy link
Author

kiddrew commented Feb 21, 2021

I tried modifying the response to use :unprocessable_entity and :see_other and it didn't fix my issue. The submit and subsequent get request both work as expected but the form render or redirect doesn't happen.

@dixpac
Copy link
Contributor

dixpac commented Feb 22, 2021

@seanpdoyle the problem is when migrating larger apps there is a lot of complex rails UJS code, which is impossible to migrate on turbo_streams because UJS is more powerful DOM manipulations wise.

So to make it work some forms need to be remote with data-turbo=false. Disabling the turbo enables old UJS code to continue working. But, trurbo-rails gem is missing this piece from the turbolinks, hence redirects are not working from the JS request.

This is maybe a different issue from the one @kiddrew posted.

@seanpdoyle
Copy link
Contributor

@dixpac thsnks for clarifying. Let's open a different issue then.

As part of the new issue, could you please help us understand why we'd want to set local: true and [data-turbo="false"], instead of [data-turbo="true"] as part of a gradual migration?

@dixpac
Copy link
Contributor

dixpac commented Feb 22, 2021

@seanpdoyle sure, I will open issue or a PR but I need more time to inspect what is happening 😄

As part of the new issue, could you please help us understand why we'd want to set local: true and [data-turbo="false"], instead of [data-turbo="true"] as part of a gradual migration?

It is local: false (remote: true) and data-turbo=false

@seanpdoyle
Copy link
Contributor

@dixpac right, is data-turbo="false" preventing Turbo support, whereas data-turbo="true" (or omitting it entirely) might opt them back in?

@dixpac
Copy link
Contributor

dixpac commented Feb 22, 2021

Yes.
Basically I need to disable turbo on the form and let Rails UJS handle the form. That works like a charm, rails UJS handles properly until I redirect inside the controller, that redirect is "broken" because turbo-rails doesn't have that "magic" concern that was included in turbolinks (trubolinks gem is removed).
I will try to execute Turbo.visit manually inside the .js.erb I think that could work.

This works on turblinks and doesn't work with turbo(remote: true, data-turbo=false):

# Form is remote: true
def create
  respond_to do |format|
    if person.save
       format.html { redirect .... } # redirect
    else
      format.js # Do some DOM manipulations with js
    end
  end
end

@seanpdoyle
Copy link
Contributor

Thanks @dixpac. Could you open a separate issue?

@uurcank
Copy link

uurcank commented Mar 13, 2021

@seanpdoyle same issue here. Turbo always handles the form even the form has data-turbo="false" format.html ignored

  if @post.save
        format.turbo_stream 
        format.html { redirect_to posts_path, notice: Post was successfully created.'}
        format.json { render :show, status: :created, location: @post}
      end

jasonfb added a commit to jasonfb/TR001 that referenced this issue Mar 13, 2021
@jasonfb
Copy link

jasonfb commented Mar 13, 2021

@Petercopter --- I see heartcombo/devise#5340 seems to fix the problem

I have steps to reproduce for Rails 6.1.3 and Devise 4.7.3 here:

https://stackoverflow.com/questions/66615478/turbo-rails-with-devise-does-not-redirect-consistently-rails-6-1-3-devise-4-7-3
My reproduction app against Devise 4.7.3 can be found here:
https://github.com/jasonfb/TR002

I'd love to be able to upgrade my Rails 6.1 app for Turbo-Rails today, but my upgrade is riddled with these Devise bugs-- mostly redirects that don't redirect in the browser but the action has happened on the backend.

I see this pull appears to fix everything (🎉 !), true? false? truthy? falsy?

in master soon? Should I wait a bit? I guess if it were in master I could point my gem to the master branch of devise? Any tips appreciated, even just a monkey patch to get me going until the fix is released.

I got all the other parts of my TR upgrade working so as you can imagine I am eager to fix this last bit. Would be happy to help if there's any way I can.

@seanpdoyle
Copy link
Contributor

@jasonfb thank you for creating https://github.com/jasonfb/TR001 to help reproduce the issue. Would it be possible for you to alter the git history so that the changes that are tied directly to reproducing the bug behavior are their own commit? It's very difficult to read through a commit that has a majority of its changes generated through Devise installation tasks.

After quickly scanning through, I have some high level questions:

  1. Are the form_for calls generating HTML with [data-remote="true"]?
  2. Is the format.turbo_stream { redirect_to "/nowhere" } call intentionally redirecting, or is it to demonstrate something else? Typically, redirect_to is better called from within the format.html { } block, or in a controller response that omits the format blocks entirely.
  3. Have you tried replacing the link_to "Log out" with a button_to instead?

@jasonfb
Copy link

jasonfb commented Mar 13, 2021

@seanpdoyle -- the whole app is to reproduce the issue. Yes I did it quickly and just made 1 commit.
it's basically not very much more than 1) the turbo-rails install, a 2) the devise install, and 3) a small bit of customization

Oh bootstrap is in there for no reason -- I can take that out if you want.

Are the form_for calls generating HTML with [data-remote="true"]?

it would appear not.
Screen Shot 2021-03-13 at 11 28 12 AM

Is the format.turbo_stream { redirect_to "/nowhere" } call intentionally redirecting, or is it to demonstrate something else?

So sorry… let me re-do, that is irrelevant now. (it was me debugging in some other way). The bug reproduces on devise directly, on the devise controller (which is not this one-- this HelloController#anything action is irrelevant, sorry).

FYI..... I think may be a devise issue related to devise because it seems like they said over there this was fixed in a PR. I will try the other things you suggest.

I removed the unneeded code, the anything action was unrelated to this --- just ignore it.

jasonfb/TR001@f141cea

@jasonfb
Copy link

jasonfb commented Mar 13, 2021

Have you tried replacing the link_to "Log out" with a button_to instead?

You are a genius (although, this is still a bug). First: the devise login partials come from the devise gem itself, so without overriding it, I cannot change the links inside of the devise partials.

Focusing on Symptom # 2 only (the Logout)--- your suggestion does indeed instantly restore the functionality.

which is interesting, and suggests to me there is a bug related to links on the Turbo side. That is, since simply changing from link_to to button_to fixed the problem with the same Devise backend controller, shouldn't a Turbo behave the same way with links as it does with buttons (seems like turbo's bug?)

As far as the rest of this, it seems like it should be addressed in devise (for one thing, if mods are to be made to the login logout form). it looks like they were discussing root cause of these issues back in January here heartcombo/devise#5325

(TBH I did not debug that part of the stack.-- so I can only speak to the part of the stack that I debugged of course but I see the thing appears to all be related to 422 status codes in Rails responses or something around this area. )

it makes sense in the sense that links are supposed to be non-destructive and buttons should do destructive things but UJS has spoiled us with method: :delete on our links.

So...... at the very least maybe Turbo could give a console error ?

@jasonfb
Copy link

jasonfb commented Mar 13, 2021

Here you go ... new example app here:

https://github.com/jasonfb/TR002

this is now without boostrap and without haml, so just enough to reproduce the Turbo-rails + Devise issue

TR002 — fsevent_watch ◂ localhost:3000)  TR002  NVM_INC=:Users:jason: nvm:versions:node:v12 8 0:include:node rvm_bin_path=:User… 2021-03-13 15-08-25

I have cross-poseted this to heartcombo/devise#5358

@jasonfb
Copy link

jasonfb commented Mar 13, 2021

you can reproduce fully on your own machine using these steps https://gist.github.com/jasonfb/eb9cf8e90514dad1af0b98e01e9bce3d

@dmitry-rychkov
Copy link

Hello everyone,

I opened #152 earlier, but I think this issue is the same.
For me it is completely not correct that Turbo prevents plain HTML forms from working with this gem:
<form action="/url" method="POST">...</form>

It is old good HTML 1.0 and it gets broken unless you wrap it into <turbo-frame> and/or make explicit turbo_stream response!
For me it doesn't matter what status code Rails return (though correct codes are a good practice), it is just broken HTML standard. Thus Turbo doesn't do progressive enhancement, it forces you into yet another ecosystem, like React, Angular and other vendor-lock tools.

This is not correct. Forms must work the same way as links. We don't need to opt-out from turbo on every link, so we shouldn't do it for every form. Links are smart enough to use whole page as response when not wrapped in turbo_frame and so should forms.

I suggest the following logic:

  1. Form tells server that it is capable of accepting turbo response (already done)
  2. On server if turbo_stream response is not defined, then use HTML response (TBD)
  3. Response may contain Turbo stream data-only or whole page, whatever - just like links work (already done for hyperlinks, for forms it works only if it is wrapped in frame, otherwise empty response is returned for some reason)
  4. After getting a response, client either redirects or renders HTML with the same logic as links do: if request was done from turbo-frame then replace frame. Otherwise replace whole HTML page. (already done for hyperlinks)

We will get everything working for all existing and all non-Rails code out of the box. It will respect old good HTML basics. And it will be progressive enhancement, not a mandatory Turbo lock-in. It will be effortless magic!

I think it is a more high-level problem of current implementation which will solve original redirect issue, too. It looks more server-side.

Sorry if I'm missing some big idea or technical restriction, but this part is confusing.

@henrik
Copy link

henrik commented Apr 14, 2021

To add to what @dmitry-rychkov said, this would also improve the Turbolinks-to-Turbo upgrade path quite a lot.

Most of it was straightforward, but having form submissions suddenly doing nothing (even without remote: true and even with status: 303) was quite a head-scratcher. The docs also seem to say that you can progressively use just Turbo Drive and later opt into more fanciness with Frames/Streams, but reality currently seems to be that you can't use just Turbo Drive on its own for form submissions.

@jasonfb
Copy link

jasonfb commented Apr 20, 2021


@dmitry-rychkov
Copy link

@jasonfb, my observation is that when server response doesn’t contain any turbo-frame, then it responds with empty response even if you generate full HTML page.

When there is a turbo-frame in response HTML then whole HTML page is returned and turbo works fine.

I couldn’t find a line in Rails code which prevents normal HTML response when there is no frame inside.

Still don’t see any reason for Turbo forms not to allow same logic as links (without any UJS)

@gharmonjr
Copy link

I ended up adding config.action_view.form_with_generates_remote_forms = false to my application.rb which prevented me needing to touch all forms by default

@tomu123
Copy link

tomu123 commented Aug 7, 2022

more than a year later and redirection still doesn't work with form when using turbo by default in rails 7, anyone got a solution?

@SleeplessByte
Copy link

SleeplessByte commented Aug 8, 2022

The solutions are in this issue.

In short: use redirect_to x, status: :see_other whenever you are redirecting from a form submission, use status: :unprocessible_entity to render errors in-line (so without redirecting), or disable turbo on the form.

@vikdotdev
Copy link

I had troubles with status: :unprocessible_entity not rendering any changes onto the screen (even though the HTML was returned from the server). It turns out turbo-rails require .html in the view template name. I had a bunch of new.slim or edit.slim without the .html part, and that was what was causing trouble.

Is this documented anywhere? Couldn't find anything about the correct template naming.

@SleeplessByte
Copy link

I think that if you don't have .html in the template, then the content_type is going to be something else. Basically rails will try to infer the content type from the rendered content.

@kiddrew
Copy link
Author

kiddrew commented Aug 12, 2022

If what @vikdotdev says is true (I haven't attempted to reproduce), seems like that should be logged as a bug. A .html.slim file extension isn't technically correct. The file isn't HTML, it's Slim. Slim converts to HTML but it is not HTML. Like coffee is to JS.

@archonic
Copy link

This would explain the trouble I had with Turbo. In an existing app which uses haml, upgrading to Turbo broke all forms which had a redirect response. Using status: :see_other never got the response to render. I still just have Turbo disabled on all forms which made me wonder what the point of upgrading from Turbolinks was. That should absolutely be a bug.

@SleeplessByte
Copy link

SleeplessByte commented Aug 12, 2022

@kiddrew

It's been like this since Rails 1 as far as I recall, so I think a non-discussion for this issue. ERB files are generally written as .html.erb, because you can also have .js.erb which is "run this JavaScript file through the ERB handler first. IRRC, you can also do things like: .json.jbuilder.erb.

I use media type negotiation a lot and this isn't inherent to Turbo or Devise, or any other library. What's new in turbo is that it "forces" content negotiation (the responds_to block), which is why you may not have ran into this before.

I don't necessarily agree with the implementation but that is the explanation.

@kiddrew
Copy link
Author

kiddrew commented Aug 12, 2022

@SleeplessByte Interesting. I admittedly have very little experience with Turbo so far. Do you know why the media type determination is different (non-existent?) in Turbo vs normal renders? Is there a technical reason or is this just a nuance of the implementation? I don't know what happens behind the scenes, but I name all my slim templates with a .slim extension and normally don't have any trouble.

@archonic
Copy link

FYI the haml / slim issue has already been confirmed here: #287.

It was exceedingly difficult to troubleshoot that issue and I only noticed because @SleeplessByte mentioned it on a thread I've been following for years.

@SleeplessByte
Copy link

SleeplessByte commented Aug 16, 2022

The confirmed post also shows that this is the convention rails expects.

TL;DR is at the bototm of this post.

@kiddrew yes! Happy to explain.
@archonic I agree. This stuff is super hard to debug too because of the massive amounts of indirection in turbo and rails.

Media type registration

So in the new turbo you will find this piece of code:

initializer "turbo.mimetype" do
Mime::Type.register "text/vnd.turbo-stream.html", :turbo_stream
end

This ensures you can call https://example.org/your/endpoint.turbo_stream to forgo content-negotiation, in the same way :html is registered to facilitate https://example.org/your/endpoint.html. It also allows you to write code like this:

respond_to do |format|
  format.turbo_stream do
    # respond to a turbo stream request
  end
  format.html do 
    # respond to an html request
  end
  format.any do
    # anything else
  end
end

When is the turbo_stream block executed? It's executed when rails understands that it "should".

  1. Because the format is explicitly set in the URL (.turbo_stream). This is not content negotiation.
  2. Following content negotiation, meaning the Accept header contains the text/vnd.turbo-stream.html media type and it is more important than anything else acceptable. In the example case, because of .any any valid media type is acceptable.

When a block is executed, it will by default set the content-type to whatever format was accepted. So, if the turbo_stream is executed, the Content-Type is set to text/vnd.turbo-stream.html if some code requests it.

Renderer creation

Often, you will not have any respond_to block in your code, but rather you'll do something like:

render json: my_json

# or

render plain: my_plain

This internally uses a Rails Renderer instead. So for example, if you want to be able to write render csv: my_csv_obj and that object be turned into csv content by calling to_csv on it, you could do something like this:

ActionController::Renderers.add :csv do |obj, options|
  filename = options[:filename] || 'data'
  str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
  send_data str, type: Mime[:csv],
    disposition: "attachment; filename=#{filename}.csv"
end

For turbo, the code is:

ActiveSupport.on_load(:action_controller) do
ActionController::Renderers.add :turbo_stream do |turbo_streams_html, options|
self.content_type = Mime[:turbo_stream] if media_type.nil?
turbo_streams_html
end
end

What this shows is: return the content to render verbatim, which means you need to do render turbo_stream: valid_html. It will, however, set the Content-Type to text/vnd.turbo-stream.html unless it's already set.

Default render

These two things together may help you understand what happens when you do not have respond_to blocks and do not call render turbo_stream: .... but rather have the following:

def show
 # render
end

Either explicit render call without a renderer option, or no render call at all (implicit render).

What happens under the hood is a chain of calls that effectively call render_to_body, usually on ActionView:

https://github.com/rails/rails/blob/04972d9b9ef60796dc8f0917817b5392d61fcf09/actionview/lib/action_view/rendering.rb#L108-L124

def _render_template(options)
  variant = options.delete(:variant)
  assigns = options.delete(:assigns)
  context = view_context

  context.assign assigns if assigns
  lookup_context.variants = variant if variant

  rendered_template = context.in_rendering_context(options) do |renderer|
    renderer.render_to_object(context, options)
  end

  rendered_format = rendered_template.format || lookup_context.formats.first
  @rendered_format = Template::Types[rendered_format]

  rendered_template.body
end

This in turn finds its template via context.in_rendering_context which comes from ActionView as well: https://github.com/rails/rails/blob/04972d9b9ef60796dc8f0917817b5392d61fcf09/actionview/lib/action_view/base.rb#L257-L274

def in_rendering_context(options)
  old_view_renderer  = @view_renderer
  old_lookup_context = @lookup_context

  if !lookup_context.html_fallback_for_js && options[:formats]
    formats = Array(options[:formats])
    if formats == [:js]
      formats << :html
    end
    @lookup_context = lookup_context.with_prepended_formats(formats)
    @view_renderer = ActionView::Renderer.new @lookup_context
  end

  yield @view_renderer
ensure
  @view_renderer = old_view_renderer
  @lookup_context = old_lookup_context
end

...which finally uses the lookup_context to see which templates are really available. It will use the provided views_paths (usually a list of all the view paths in your project) to find files and tries to match these things:

https://github.com/rails/rails/blob/04972d9b9ef60796dc8f0917817b5392d61fcf09/actionview/lib/action_view/lookup_context.rb#L43-L52

  • locale (like en)
  • formats (like html)
  • variants (like mobile)
  • handlers (like haml)

We can go deeper, but the important thing to understand is that a file index.html+mobile.haml will be _processed (handled) by haml, only be matchable if the variant requested is mobile, and will output html (because that is the format).

The matched template will actually set the format (see @rendered_format above).

Turbo, and why index.haml fails

Okay. Final step. Turbo doesn't use native browser navigation but rather a fetch(y) request. It will create a special request. This will happen first when a form is being submitted, first via requestStarted which dispatches the turbo:submit-start event, and then via start, which, if allowed (confirm) will call the fetch request perform function.

(We're almost at the point where everything lines up).

This function calls prepareHeadersForRequest on the form submission which basically checks if the form will understand a Turbo Streams response and if so adds text/vnd.turbo-stream.html to the Accept header. If it doesn't, then it only accepts text/html, application/xhtml+xml.

WHY does it accept turbo streams? Because of this line:
https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/form_submission.ts#L224

  • The form submission is not idempotent (aka a POST) <-- this.
  • Or some data attribute is present

Your controller does it's action and then redirects to a new location by passing the location header. After it has redirected, it renders the index action and the response is sent back to the client, right?

The code that determines what gets called when it returns can be found here:
https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/form_submission.ts#L180-L196

Either it succeeds or it fails, the delegated functions are here: https://github.com/hotwired/turbo/blob/aeeaae8edb5f6ec6ef6b19eaf93dc060a1e67b10/src/core/drive/navigator.ts#L92-L126.

The particular lines that we care about are:

const responseHTML = await fetchResponse.responseHTML
if (responseHTML) {
  // do stuff
}

That responseHTML property is defined here: https://github.com/hotwired/turbo/blob/aeeaae8edb5f6ec6ef6b19eaf93dc060a1e67b10/src/http/fetch_response.ts#L50-L56

get isHTML() {
  return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/)
}

get responseHTML(): Promise<string | undefined> {
  if (this.isHTML) {
    return this.response.clone().text()
  } else {
    return Promise.resolve(undefined)
  }
}

This basically says: if the content type is text/html or the xhtml equiv, then I know it's HTML and I can interpret it. In that case only process the response and let turbo visit the new content. It does match text/vnd.turbo-stream.html as well, (and anything that ends with html).

Okay. So. Why doesn't it then?

  • You use implicit render in your controller
  • Implicit render will try to match the current request which has Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml. The order is significant.
  • It cannot find any file that has a format of .turbo_stream or .html. So then it tries to look for a less specific thing. It finds a file without format called index.haml.
  • When the template is picked it will use the file's format OR THE FIRST FORMAT FROM THE ACCEPT HEADER (refresher: rendered_format = rendered_template.format || lookup_context.formats.first)
  • When you're using implicit render, it will USE THE FORMAT THAT THE RENDERER RENDERED.
  • The haml is rendered with Content-Type: text/vnd.turbo-stream.html.
  • Turbo receives the HTMl and starts interpreting it as a stream...
  • ...a stream requires something to stream into, but you're rendering a document.
  • It doesn't render.

Hope it helps.

TL;DR

  1. Turbo adds support for turbo streams because it wants to be able to respond to a stream response which is allowed because the form is a POST.
  2. It does this by adding a media type to the Accept header.
  3. Your files always assumed you're rendering a single media type (called action.handler instead of action.format.handler).
  4. Rails will always use the first format if none was given in a file name.
  5. Because turbo stream value is prepended, your rendered view will be rendered as (Content-Type) a turbo stream.
  6. Turbo will not allow you to render the response because it thinks it's a stream responses, without a place to stream to and because it can only render a document HTML.

@kiddrew
Copy link
Author

kiddrew commented Aug 17, 2022

@SleeplessByte thanks for the thorough explanation!

@SleeplessByte
Copy link

There was a slight error in the final section which I've since fixed. TL;DR stays the same :)

@johnykov
Copy link

johnykov commented Aug 19, 2022

I've stumbled upon this issue yesterday. My App relies on 2 format renderers in update method in controller: turbo_stream and html. In one case I do post form with rails-ujs from haml template (submit button on top of the page, above form) and CTRL instead of html renderer it always used turbo_stream. By some kind of intuition I've re-ordered renderers and it worked. And today in the morning I've discovered great explanation of why it worked - default renderer. Great job @SleeplessByte ! Thx a ton

@danielricecodes
Copy link

I had troubles with status: :unprocessible_entity not rendering any changes onto the screen (even though the HTML was returned from the server). It turns out turbo-rails require .html in the view template name. I had a bunch of new.slim or edit.slim without the .html part, and that was what was causing trouble.

Is this documented anywhere? Couldn't find anything about the correct template naming.

FYI - its unprocessable_entity. an a not an i.

@danielricecodes
Copy link

danielricecodes commented Sep 2, 2022

@SleeplessByte - I really appreciate the thorough response but this issue is maddening. On a brand new Rails 7.0.3.1 app I used rails generate scaffold quote name:string which generates the following:

class QuotesController < ApplicationController
  before_action :set_quote, only: %i[ show edit update destroy ]

  # GET /quotes or /quotes.json
  def index
    @quotes = Quote.all
  end

  # GET /quotes/1 or /quotes/1.json
  def show
  end

  # GET /quotes/new
  def new
    @quote = Quote.new
  end

  # GET /quotes/1/edit
  def edit
  end

  # POST /quotes or /quotes.json
  def create
    @quote = Quote.new(quote_params)

    respond_to do |format|
      if @quote.save
        format.html { redirect_to quote_url(@quote), notice: "Quote was successfully created." }
        format.json { render :show, status: :created, location: @quote }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @quote.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /quotes/1 or /quotes/1.json
  def update
    respond_to do |format|
      if @quote.update(quote_params)
        format.html { redirect_to quote_url(@quote), notice: "Quote was successfully updated." }
        format.json { render :show, status: :ok, location: @quote }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @quote.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /quotes/1 or /quotes/1.json
  def destroy
    @quote.destroy

    respond_to do |format|
      format.html { redirect_to quotes_url, notice: "Quote was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_quote
      @quote = Quote.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def quote_params
      params.require(:quote).permit(:name)
    end
end

NONE OF THE SUGGESTIONS ABOVE ARE PRESENT IN THIS FILE, YET THE FORMS AND EVERYTHING WORKS OUT OF THE BOX. I do not need to copy the view template code into this reply because it's all unedited scaffold. In a Rails 7 scaffold controller, there is no :see_other, there are no respond_to blocks, and it is using implicit rendering....and yet....scaffold works out of the box while upgraded apps are horked. 😡

That said, it is extremely challenging to tell what the problem is with my recently upgraded Rails 7 app when such a basic scaffold works perfectly and without any of the above suggestions. My Rails 7 app has @rails/ujs removed and there are no remote forms. Nothing with Rails UJS is interfering here. For all intents and purposes, my Rails 7 app's forms should be working but they are not.

I will keep working on it and post my solution when I find it but good gravy this issue needs to be fixed or the upgrade docs need to be a lot more clearer than they are.

@laptopmutia
Copy link

laptopmutia commented Sep 3, 2022

The solutions are in this issue.

In short: use redirect_to x, status: :see_other whenever you are redirecting from a form submission, use status: :unprocessible_entity to render errors in-line (so without redirecting), or disable turbo on the form.

could u help me sir? I have already add status: :see_other but still the page is not redirected

  def lock
    if @enrollment.update(wlkp_params.merge(lock: true))
      respond_to do |format|
        format.html { redirect_to ranking_path, notice: "Pendaftaran berhasil disimpan", status: :see_other}
        # format.turbo_stream { flash.now[:notice] = "Pendaftaran berhasil di submit." }
      end
    else
      render "enrollments/current", notice: "Error harap hubungi admin", status: :unprocessable_entity
    end
  end

I could see the response of 303/302 in inspect element networks and the 200 ok of the redirected page but my browser window still show the game page

================================

I have found the root problem and solution for this

this is because my response is not enough have html element so turbo not behave accordingly

when I add more html code to the response its redirected normaly

@SleeplessByte
Copy link

@danielricecodes what's the file names of the views? They are probably .html.erb.

What are your file names?

@glaszig
Copy link

glaszig commented Jan 19, 2023

@kiddrew is your simple_form_for inside any turbo-frame? if so, turbo will load the redirect via ajax and not do any actual redirect or Turbo.visit().

@danielricecodes i just tried your scaffold example in a fresh rails 7 app. everything works. like with turbolinks. but there also is no turbo-frame at play.

i was scimming through turbo's code quickly and from what i've seen the following happens:

everything inside a turbo-frame tag is handled through a FrameController. it'll submit forms, detect redirects, load the resource redirected to, scan the response for turbo frames and replace those on the page. that's why you won't see any other change on the page.

now, for a form outside of a turbo-frame, the Navigation class will handle the submission and actually do a redirect (or execute a Turbo.visit()).

so, there you have it. you cannot break out of a turbo-frame. which is unfortunate i think.

my use case:

  • load modal content into a turbo-frame
  • do everything inside that frame (have a form, submit form, handle validation error)
  • do an actual redirect/Turbo.visit() upon successful submit

@danielricecodes
Copy link

danielricecodes commented Jan 20, 2023

@danielricecodes what's the file names of the views? They are probably .html.erb.

What are your file names?

This was actually the problem. Wow 🤯

My editor was saving files with only the .slim extension. Explicitly using .html.slim fixed my app.

@awolfson
Copy link

awolfson commented Feb 11, 2023

A million thanks to @SleeplessByte for thoroughly demystifying this issue that folks have been battling against for—checks date when this issue was created—two years and one day! I wish there was a way to pin your answer to the top, especially since it feels like there's unlikely to be any remediation (unless I've missed something).

(If you haven't read it, read it.)

It seems like maybe not prepending the Turbo Streams content type would fix it, though, right? Assuming that would not break Turbo Streams, I would like to see that happen, and here's why.

The reason that so many are reporting that form redirection is broken out of the box is that we are following two longstanding Rails conventions in our new apps:

  1. Redirection with implicit render
  2. Format-less template filenames

But because we now want to allow requests for Turbo Streams to work seamlessly, we've disrupted this pattern, so that you have to change one of those two conventions in your app just to get form submissions to work.

Wouldn't it be better to shift the burden onto the newer feature, Turbo Streams, by appending it to the Accept header instead of prepending it?

@SleeplessByte
Copy link

SleeplessByte commented Feb 11, 2023

Yes and no.

Prepending signals "I prefer a turbo stream response" which you do. If you'd append it and the controller can return a html response, it will, despite a turbo stream response being available.

The real issue is that rails is defaulting to turbo stream implicitly. That's the real bug!

Edit: rails is returning a full document as a stream which it should do imo. Unless you explicitly opt in.

@supaplexer
Copy link

supaplexer commented Mar 4, 2023

I've just gone through every comment and I think my problem differs in a way that my create action was correctly redirecting with turbo-rails 1.3.3 but stopped doing so after upgrading it. After upgrading the gem to 1.4.0 I can still see the 302 response but it's actually staying in the same view and updating the turbo-frame with "Content missing". Here's the code:

def create
    @msg = current_user.msgs.new(msg_params)

    respond_to do |format|
      if @msg.save
        format.html { redirect_to msgs_path, notice: 'Message was created.' } # <-- this was fine in 1.3.3
      else
        format.turbo_stream do
          flash.now[:alert] = "An error occurred"
          render turbo_stream: render_error
        end
        format.html do
          redirect_to msgs_path, alert: "An error occurred"
        end
      end
    end
  end

As you can see it's pretty basic and I just have an html response when a message is saved. Of course the form is submitting a POST.

@koenhandekyn
Copy link

koenhandekyn commented Mar 6, 2023

    format.html { redirect_to msgs_path, notice: 'Message was created.' } # <-- this was fine in 1.3.3

fighting the same issue here

as a fix, if we explicitly add format :html to the path, then the behaviour is 'ok' again, but it's not really nice to have ".html" appearing in the path suddenly.

redirect_to client_sheets_path(format: :html), flash: { notice: I18n.t("controller.orders.create.order_created") }, status: :see_other

@awolfson
Copy link

awolfson commented Mar 6, 2023

    format.html { redirect_to msgs_path, notice: 'Message was created.' } # <-- this was fine in 1.3.3

fighting the same issue here

as a fix, if we explicitly add format :html to the path, then the behaviour is 'ok' again, but it's not really nice to have ".html" appearing in the path suddenly.

redirect_to client_sheets_path(format: :html), flash: { notice: I18n.t("controller.orders.create.order_created") }, status: :see_other

@koenhandekyn What is the name of your template file that renders at client_sheets_path? If it doesn't have .html in the filename, you should be able to add it, and remove format: :html from the redirect. For example, renaming index.haml to index.html.haml.

@koenhandekyn
Copy link

@awolfson that worked indeed. i'm a bit puzzled still as i thought it did the 'same' by make the respond_to :html explicit on that controller end point (and that didn't seem to help). - and also i remember reading at some point advice/documentation against adding the double extension :) -

@awolfson
Copy link

awolfson commented Mar 7, 2023

@awolfson that worked indeed. i'm a bit puzzled still as i thought it did the 'same' by make the respond_to :html explicit on that controller end point (and that didn't seem to help). - and also i remember reading at some point advice/documentation against adding the double extension :) -

@koenhandekyn Yeah, you're not supposed to need the double extension, that's part of the bug. You'll want to read this post for the full explanation of what's happening: #122 (comment)

@QueeniePeng
Copy link

Is anyone else experiencing an issue where they want to use turbo-confirm to add a confirmation dialog before archiving a record, while also keeping turbo: false so they can redirect to a new page if the record is saved? The form is within a turbo_frame_tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests