Rails 嵌套表单提交但不持久化数据

Posted

技术标签:

【中文标题】Rails 嵌套表单提交但不持久化数据【英文标题】:Rails Nested Form submits but does not persist the data 【发布时间】:2020-08-02 00:17:00 【问题描述】:

我正在尝试让我的嵌套表单工作。这是一张新专辑的表格,下面有一个可以写评论的空间。表单提交并且相册显示在页面上,但评论没有,它只是显示为空白。我在日志“Unpermitted parameter: reviews_attributes”中只收到一个错误

日志:

Started POST "/albums" for ::1 at 2020-04-19 12:10:58 -0400
Processing by AlbumsController#create as html
  Parameters: "authenticity_token"=>"jYHM+yeExcTJtENvjQBDsOMo8Ig1g5bRa+hYZ9kCkiI4NO3KP3xdV7SpSZ2IeIOp0wC+5WLxflu22NTIXtoibg==", "album"=>"artist"=>"Blink 182", "title"=>"California", "avatar"=>#<ActionDispatch::Http::UploadedFile:0x00007fc372d8d5f0 @tempfile=#<Tempfile:/var/folders/26/p006tryd6yb9sp9rq446p07c0000gn/T/RackMultipart20200419-64975-1bp8ang.jpg>, @original_filename="71GfPCWJHXL._SL1500_.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"album[avatar]\"; filename=\"71GfPCWJHXL._SL1500_.jpg\"\r\nContent-Type: image/jpeg\r\n">, "reviews_attributes"=>"0"=>"title"=>"Blink 182 review", "date"=>"2020-04-19", "content"=>"NOT THE BLINK182 I KNOW AND LOVE WHERE IS ToM BRING BACK TOM", "commit"=>"Create Album"
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 11], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:10:in `current_user'
Unpermitted parameter: :reviews_attributes
   (0.1ms)  begin transaction
  ↳ app/controllers/albums_controller.rb:29:in `create'
  Album Create (0.4ms)  INSERT INTO "albums" ("artist", "title", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["artist", "Blink 182"], ["title", "California"], ["created_at", "2020-04-19 16:10:58.838672"], ["updated_at", "2020-04-19 16:10:58.838672"]]
  ↳ app/controllers/albums_controller.rb:29:in `create'
  ActiveStorage::Blob Load (0.3ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 6], ["record_type", "Album"], ["name", "avatar"], ["LIMIT", 1]]
  ↳ app/controllers/albums_controller.rb:29:in `create'
  ActiveStorage::Attachment Load (0.2ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 6], ["record_type", "Album"], ["name", "avatar"], ["LIMIT", 1]]
  ↳ app/controllers/albums_controller.rb:29:in `create'
  ActiveStorage::Blob Create (0.3ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES (?, ?, ?, ?, ?, ?, ?)  [["key", "l22w59ulprgmmqrs025woawhi1h6"], ["filename", "71GfPCWJHXL._SL1500_.jpg"], ["content_type", "image/jpeg"], ["metadata", "\"identified\":true"], ["byte_size", 137504], ["checksum", "IXxJAt318tAPkwmaLBUW/A=="], ["created_at", "2020-04-19 16:10:58.852999"]]
  ↳ app/controllers/albums_controller.rb:29:in `create'
  ActiveStorage::Attachment Create (0.4ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?)  [["name", "avatar"], ["record_type", "Album"], ["record_id", 6], ["blob_id", 11], ["created_at", "2020-04-19 16:10:58.856948"]]
  ↳ app/controllers/albums_controller.rb:29:in `create'
  Album Update (0.2ms)  UPDATE "albums" SET "updated_at" = ? WHERE "albums"."id" = ?  [["updated_at", "2020-04-19 16:10:58.862445"], ["id", 6]]
  ↳ app/controllers/albums_controller.rb:29:in `create'
   (3.3ms)  commit transaction
  ↳ app/controllers/albums_controller.rb:29:in `create'
  Disk Storage (2.5ms) Uploaded file to key: l22w59ulprgmmqrs025woawhi1h6 (checksum: IXxJAt318tAPkwmaLBUW/A==)
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: 9114fadd-5897-4939-9cd6-1d0751f129b2) to Async(active_storage_analysis) with arguments: #<GlobalID:0x00007fc372ddf5d0 @uri=#<URI::GID gid://review-project/ActiveStorage::Blob/11>>
Redirected to http://localhost:3000/albums/6
Completed 302 Found in 63ms (ActiveRecord: 5.5ms | Allocations: 15980)

嵌套表单(albums/_form.html.erb)

<%= form_for(@album) do |f| %> 
<% if @album.errors.any? %>
<ul>
  <% @album.errors.full_messages.each do |msg| %>
    <li><%= msg %></li>
  <% end %>
</ul>
<% end %>
      <%= f.label :artist %>
      <%= f.text_field :artist %>
            <br><br>
      <%= f.label :title %>
      <%= f.text_field :title %>
            <br><br>
      <%= f.label "Album Image:" %><br>
      <%= f.file_field :avatar %>
           <br><br>
            <h2>Write your review of the album</h2>
            <%= f.fields_for :reviews do |ff| %>
            <%= ff.label :title %>
            <%= ff.text_field :title %>
            <br>
            <%= ff.label :date %>
            <%= ff.date_field :date %>
            <br>
            <%= ff.label :content %>
            <%= ff.text_area :content %>
            <% end %>
            <br>
      <%= f.submit %>
<% end %>
<br><br><br>
<%= link_to "Back to Album", albums_path(@album) %>

相册控制器:

class AlbumsController < ApplicationController
    before_action :set_album, only: [:show, :edit, :update, :destroy]
    before_action :must_login, only: [:new, :show, :create, :edit, :update, :destroy]

    def index
        @albums = Album.all
        @user = current_user
    end

    def show
        @review = @album.reviews.build
        @review.user = current_user

        @review.save
        @reviews = Review.recent #scope
    end

    def new
        @album = Album.new
        @review = @album.reviews.build
        @user = current_user
    end

    def create
        #@user = User.find(current_user.id)
        @album = current_user.albums.build(album_params)
        #@album.user_id = current_user.id
        @album.reviews.each  |r| r.user ||= current_user  # I'm using ||= so i can use the same code on update without changing reviews that already have a user
        if @album.save
            redirect_to album_path(@album)
        else
            render :new
        end
    end

    def edit
        @user = current_user
    end

    def update
        #@album = current_user.albums.build(album_params)
        @album.user_id = current_user.id
        if @album.update(album_params)
            redirect_to album_path(@album), notice: "Your album has been updated."
        else
            render 'edit'
        end
    end

    def destroy
        @album.delete
        @album.avatar.purge
        redirect_to albums_path
    end

    private

    def set_album
        @album = Album.find(params[:id])
    end

    def album_params
        params.require(:album).permit(:artist, :title, :avatar, :user_id, review_attributes:[:title, :date, :content])
    end
end

评论控制者

class ReviewsController < ApplicationController
    before_action :set_review, only: [:show, :edit, :update, :destroy]
    before_action :set_current_user, only: [:index, :show, :new, :edit, :destroy]
    before_action :find_album, only: [:show, :create, :edit, :update, :destroy]
    before_action :must_login, only: [:index, :show, :new, :create, :edit, :update, :destroy]

    def index
        @albums = Album.with_recent_reviews
    end

    def show

        #@reviews = Review.where("album_id = ?", params[:album_id])
    end

    def new
        if params[:album_id] && @album = Album.find_by(id: params[:client_id])
            @review = @album.reviews.build
        else
            redirect_to albums_path
        end
    end

    def create
        @review = current_user.reviews.build(review_params)
        @review.album = @album
        if @review.save
            redirect_to album_path(@album)
        else
            @album = @review.album
            render :new
        end
    end

    def edit
    end

    def update
        if @review.update(review_params)
            redirect_to album_path(params[:album_id])
        else
            render 'edit'
        end
    end

    def destroy
        if current_user.id == @review.user_id
          @album.reviews.find(params[:id]).destroy
          redirect_to album_path(params[:album_id])
        else
           flash[:error] = "Unable to delete your review. Please try again."
           redirect_to album_reviews_path(@review)
        end
      end

    private

    def set_review
        @review = Review.find(params[:id])
    end

    def set_current_user
        @user = current_user
    end

    def find_album
        @album = Album.find(params[:album_id])
    end

    def review_params
        params.require(:review).permit(:title, :date, :content, album_attributes:[:artist, :title, :user_id])
    end

end

专辑型号:

class Album < ApplicationRecord
    has_many :reviews
    has_many :users, through: :reviews
    has_one_attached :avatar
    accepts_nested_attributes_for :reviews
    validates_presence_of :artist
    validates_presence_of :title
    scope :with_recent_reviews, ->  includes(:reviews).where(reviews:  date: [(Date.today - 7.days)..Date.tomorrow] )  #scope relies on include method and custom query on related model (reviews)
end

审查模型:

class Review < ApplicationRecord
    belongs_to :album, optional: true
    belongs_to :user
    validates_presence_of :content
    validates :title, presence: true, uniqueness: true
    validates :date, presence: true
    accepts_nested_attributes_for :album
    scope :recent, ->  where("date(date) >= ?", Date.today - 7.days)  #scope
end

Routes.rb

Rails.application.routes.draw do
  get '/auth/:provider/callback' => 'sessions#omniauth'
  get 'auth/failure', to: redirect('/')
  get '/signup' => 'users#new', as: 'signup'
  post '/signup' => 'users#create'
  get '/signin' => 'sessions#new'
  post '/signin' => 'sessions#create'
  get '/signout' => 'sessions#destroy'
  post '/logout', to: "sessions#destroy"

  resources :albums do
    resources :reviews, except: [:index]
  end
  resources :users, only: [:show, :destroy]
  resources :reviews, only: [:index]

  root to: "albums#index"
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

【问题讨论】:

【参考方案1】:

reviews 关联是 has_many,因此 fields_for 应该使用复数形式。

= f.fields_for :reviews do |ff|

这样,rails 会创建您允许的参数reviews_attributes

如果您对该更改仍有问题,请显示新的错误和堆栈跟踪。

编辑: 如果您想将当前用户 id 设置为评论的用户(类似于您将当前用户指定为专辑创建者的操作),您可以在保存记录之前指定它:

def create
    # @user = User.find(current_user.id) you don need this, you already have the user at current_user, no need to find it again
    @album = current_user.albums.build(album_params)
    # @album.user = current_user you don't need this, current_user.albums.build already sets this
    @album.reviews.each  |r| r.user ||= current_user  # I'm using ||= so you can use the same code on update without changing reviews that already have a user
    if @album.save
        redirect_to album_path(@album)
    else
        render :new
    end
end

此外,从reviews_attributes 的允许参数中删除:user_id:album_id,您不希望用户利用该分配添加您实际上不使用的参数

【讨论】:

在此处将 review_attributes 更改为 reviews_attributes params.require(:album).permit(:artist, :title, :avatar, :user_id, review_attributes:[:title, :date, :content, :user_id, :album_id]) 我这样做了,在我的表单上显示了一个错误,上面写着“评论用户必须存在”。需要明确的是,这就是发生的事情ibb.co/jw3wgjn 你有belongs_to :user,你允许user_id 参数,但你没有在任何地方设置用户ID。 您是否将review_attributes 更改为reviews_attributes?显示带有请求和错误的服务器日志以及当前的album_params 方法,更新问题 我仍然在您的 album_params 方法中看到 review_attributes。你真的改变了吗?确保它是复数

以上是关于Rails 嵌套表单提交但不持久化数据的主要内容,如果未能解决你的问题,请参考以下文章

Rails 5 成功发送数据后重新启用表单提交

提交已作为对象存在的表单数据(rails3.22)

React Hook Form:提交带有嵌套组件的表单或提取嵌套组件的字段提交

Rails ActiveAdmin active_admin_form_for 提交按钮已禁用

使用 jQuery 星形选择器的 Rails 表单

jsp页面的form表单action提交数据但不跳转页面,后台返回的数据需要在当前页进行处理,怎么做?