1

In the app I am building, the user is meant to be redirected to the "show" page when they click on the "replies" section, which looks like this:

<%= link_to tweet_path(tweet_presenter.tweet), class: "text-decoration-none text-black replies" do %>
  <%= image_tag "speech.png", size: "20x20", class: "me-1" %>
  <span>Replies<span>
 <% end %>

... and forms part of a view partial (_tweet.html.erb) which is enclosed in a turbo_frame that opens so: <%= turbo_frame_tag dom_id(tweet_presenter.tweet) do %>

The redirection does not occur, however. Instead, I obtain the following error message:

Uncaught (in promise) Error: the response (200) did not contain the expected <turbo-frame id=“tweet_1” and will be ignored…

I have checked that the link_to tweet_path(tweet_presenter.tweet) path is the correct one by running rails routes, where I obtain the following:

tweet GET /tweets/:id(.:format) tweets#show

The output from the console, displays that the correct tweet_id is being accessed, despite what the error message says:

Started GET "/tweets/1" for ::1 at 2023-04-10 15:53:46 -0700
Processing by TweetsController#show as HTML
  Parameters: {"id"=>"1"}
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:2:in `block in <class:ApplicationController>'
  Tweet Load (0.3ms)  SELECT "tweets".* FROM "tweets" WHERE "tweets"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/tweets_controller.rb:5:in `show'
  Rendering layout /Users/doracobian/.rvm/gems/ruby-3.1.1/gems/turbo-rails-1.4.0/app/views/layouts/turbo_rails/frame.html.erb
  Rendering tweets/show.html.erb within layouts/turbo_rails/frame
  Rendered tweets/show.html.erb within layouts/turbo_rails/frame (Duration: 44.9ms | Allocations: 762)
  Rendered layout /Users/doracobian/.rvm/gems/ruby-3.1.1/gems/turbo-rails-1.4.0/app/views/layouts/turbo_rails/frame.html.erb (Duration: 46.0ms | Allocations: 853)
Completed 200 OK in 52ms (Views: 46.8ms | ActiveRecord: 0.5ms | Allocations: 2490)

...and looking at the "elements" in the developer tools, I see turbo-frame#tweet_1, again, contradicting what the error message tells me.

What is going on? I'm going around in circles trying to figure this out.

For whoever takes the trouble to look into this quandary of mine, here is what the pertinent portion of routes.rb looks like:

 resources :tweets, only: [:show, :create] do
    resources :likes, only: [:create, :destroy]
    resources :bookmarks, only: [:create, :destroy]
    resources :retweets, only: [:create, :destroy]
  end

... the "show" portion of the tweets_controller.rb:

def show
    @tweet = Tweet.find(params[:id])
  end

Very grateful for any input on this.

German
  • 91
  • 6
  • i see `show.html.erb` is rendered. you don't have `` on that page. what you see in the elements inspector is not what you get as a response: https://stackoverflow.com/a/75916578/207090 – Alex Apr 13 '23 at 08:06
  • That is just what puzzles me. The console says that `show.html.erb` has been rendered, but I see no indication of that on the monitor. As to ``, I have no idea how I can transfer it to where it needs to go. – German Apr 13 '23 at 19:55

1 Answers1

1

The frame is always going to be there, regardless of what you get as a response. If there is a frame with the same id in the response than it will be updated, if not, you will get frame missing error. To see what you actually get as a response you have to look at the network tab:

https://stackoverflow.com/a/75916578/207090

Just to make it clear, if you have this on the index page:

<!-- app/views/tweets/index.html.erb -->

<turbo-frame id="new_tweet">
  <a href="/tweets/new">New tweet</a>
</turbo-frame>

Then you need the same frame on the page that you're requesting or redirecting to:

<!-- app/views/tweets/new.html.erb -->

<turbo-frame id="new_tweet">
  <form  action="/tweets"  method="post">
    <textarea name="tweet[content]"></textarea>
    <input type="submit" name="commit" value="Create Tweet">
  </form>
</turbo-frame>

As I understood, your turbo frame is in _tweet.html.erb partial and I don't see that partial being rendered from your show page.


Here is my tweetster clone. Create new tweets from the index page in a frame, then insert newly created tweet with turbo stream. Same thing for replies and replies on replies and it works forever.

There are also so many ways to set this up, this is just one that came to mind.

# db/migrate/20230413231722_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[7.0]
  def change
    create_table :tweets do |t|
      t.text :content
      t.references :tweet
    end
  end
end

# app/models/tweet.rb
class Tweet < ApplicationRecord
  validates :content, presence: true
  has_many :replies, class_name: "Tweet", dependent: :destroy
  belongs_to :tweet, optional: true
  def reply?
    tweet_id.present?
  end
end

# config/routes.rb
resources :tweets do
  get :reply, on: :member
end

You can pretty much ignore this controller, I've only added reply action and turbo_stream format to create, because after the tweet is created we're done with the frame and we have to make our great escape from it:

# app/controllers/tweets_controller.rb

class TweetsController < ApplicationController
  def index # GET /tweets
    @tweets = Tweet.where(tweet_id: nil) # ignore replies
  end

  def new  = tweet # GET /tweets/new
  # show and edit are not used
  def show = tweet # GET /tweets/1
  def edit = tweet # GET /tweets/1/edit

  def reply # GET /tweets/1/reply
    render partial: "form", locals: {tweet: tweet.replies.new}
  end

  def create # POST /tweets
    respond_to do |f|
      if tweet(tweet_params).save
        # NOTE: render `create.turbo_stream.erb` where we'll handle some
        #       things regarding turbo_frame
        f.turbo_stream
        f.html { redirect_to tweet_url(tweet), notice: "Saved." }
      else
        f.html { render tweet.new_record? ? :new : :edit, status: 422 }
      end
    end
  end
  alias_method :update, :create # PATCH/PUT /tweets/1

  def destroy # DELETE /tweets/1
    tweet.destroy
    respond_to do |f|
      f.turbo_stream { render turbo_stream: turbo_stream.remove(tweet) }
      f.html { redirect_to tweets_url, notice: "Destroyed." }
    end
  end

  private

  def tweet attributes = {}
    @tweet ||= begin
      model = params[:id] ? Tweet.find(params[:id]) : Tweet.new
      model.attributes = attributes
      model
    end
  end

  def tweet_params = params.require(:tweet).permit!
end
# app/views/tweets/index.html.erb
<%= link_to "New tweet", new_tweet_path, data: {turbo_frame: :new_tweet} %>
<%= turbo_frame_tag :new_tweet %>
<%= tag.div id: :tweets, class: "grid gap-4 mt-4" do %>
  <%= render @tweets.order(id: :desc) %>
<% end %>

# app/views/tweets/new.html.erb
<%= render "form", tweet: @tweet %>

# app/views/tweets/_form.html.erb
<%# NOTE: figure out correct turbo frame id for replies and tweets %>
<% frame_id = tweet.reply? ? dom_id(tweet.tweet, :new_reply) : dom_id(tweet) %>
<%= turbo_frame_tag frame_id do %>
  <%= form_with model: tweet, class: {"ml-8": tweet.reply?} do |f| %>
    <%= tag.div safe_join(f.object.errors.full_messages, tag.br), class: "text-red-500" if f.object.errors.any? %>
    <%= f.hidden_field :tweet_id %>
    <%= f.text_area :content, placeholder: (tweet.reply? ? "reply..." : "new tweet..."), class: "rounded-xl w-full" %>
    <%= f.button "Save", class: "font-bold" %>
  <% end %>
<% end %>

# app/views/tweets/_tweet.html.erb
<%= tag.div id: dom_id(tweet), class: ["grid gap-4", {"ml-8": tweet.reply?}] do %>
  <%= tag.div class: "flex justify-between rounded-xl shadow border p-4" do %>
    <%= tweet.content %>
    <%= tag.div class: "flex gap-2" do %>
      <%= link_to "reply", reply_tweet_path(tweet), data: {turbo_frame: dom_id(tweet, :new_reply)} %>
      <%= button_to "delete", tweet_path(tweet), method: :delete, class: "hover:text-red-500" %>
    <% end %>
  <% end %>
  <%= turbo_frame_tag dom_id(tweet, :new_reply), class: "contents" %>
  <%= tag.div id: dom_id(tweet, :replies) do %>
    <%= render tweet.replies %>
  <% end %>
<% end %>

# i'm only using tag.div for syntax highlight for SO (erb + html tags don't mix well)

This is probably the main part where you need to make things happen outside of the frame, maybe replace a frame with a different frame, maybe append a flash alert somewhere, etc:

# app/views/tweets/create.turbo_stream.erb

<%# there is probably a way to make it the same for tweets and replies %>
<% if @tweet.reply? %>
  <% parent_tweet = @tweet.tweet %>
  <%= turbo_stream.update dom_id(parent_tweet, :new_reply) %>
  <%= turbo_stream.append dom_id(parent_tweet, :replies), @tweet %>
<% else %>
  <#% this v is for tweets and same ^ for replies %>

  <%# NOTE: this `update` will remove content from turbo_frame %>
  <%#       but you can add whatever updates you need here     %>
  <%= turbo_stream.update :new_tweet %>

  <%# NOTE: this will add new tweet to `<div id="tweets">` %>
  <%= turbo_stream.prepend :tweets, @tweet %>
<% end %>

If you want to "just" redirect out of the frame:

https://stackoverflow.com/a/75750578/207090

Alex
  • 16,409
  • 6
  • 40
  • 56