Rails 5 - 保存回滚,因为嵌套模型父模型没有在子模型之前保存 [英] Rails 5 - save rolls back because nested models parent model is not being saved before child model

查看:46
本文介绍了Rails 5 - 保存回滚,因为嵌套模型父模型没有在子模型之前保存的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好吧,Rails 5 确实与 Rails 4 有细微差别.我所要做的是,每次我单击表单上的提交按钮时,它都会重新加载错误个人资料用户必须存在个人资料用户不能为空.该表单加载良好,包括嵌套模型表单,但无论出于何种原因,它都无法在尝试将具有以下输出的子模型保存到控制台之前保存父模型:

Puma 单人模式启动...* 版本 3.7.0 (ruby 2.2.6-p396),代号:Snowy Sagebrush* 最小线程数:5,最大线程数:5* 环境:开发* 监听 tcp://0.0.0.0:3000使用 Ctrl-C 停止在 2017-03-09 18:51:04 -0500 开始为 192.168.0.31 发布/users"无法从 192.168.0.31 渲染控制台!允许的网络:127.0.0.1、::1、127.0.0.0/127.255.255.255ActiveRecord::SchemaMigration Load (0.2ms) SELECT `schema_migrations`.* FROM `schema_migrations`由 UsersController#create 处理为 HTML参数:{"utf8"=>"✓", "authenticity_token"=>"JPKO+ppAYqwWS8tWeXhEtbUWynXREu9jYlF0KIlyPgUaabHSzjPZocSxCvr/WEm1r6wAQyT1CvA6hNk,>"用户名=>"用户名="密码>[过滤]",user_type_id"=>1",profile_attributes"=>{first_name"=>123",middle_name"=>123",last_name"=>;"123", "email"=>"123@123.com", "phone_number"=>"1234567890", "cell_number"="1234567890"}}, "commit"="创建用户"}(0.1ms) 开始(0.2ms) 回滚在布局/应用程序中呈现 users/new.html.erb呈现的 users/_form.html.erb (112.5ms)在布局/应用程序中呈现 users/new.html.erb (118.7ms)在 834ms 内完成 200 OK(查看:780.1ms | ActiveRecord:2.2ms)

我在这段关系中遇到了其他问题,我想也许我需要重建这个项目.以下是围绕此问题的所有相关代码:

############################################################################## 用户模型###########################################################################类用户<申请记录has_one :profile, inverse_of: :useraccepts_nested_attributes_for :profile, allow_destroy: true结尾############################################################################## 配置文件模型###########################################################################班级简介申请记录所属:用户,inverse_of::个人资料validates_presence_of : 用户结尾############################################################################## 用户控制器###########################################################################类用户控制器 <应用控制器before_action :set_user, only: [:show, :edit, :update, :destroy]# 获取/用户# 获取/users.json定义索引@users = User.all结尾# 获取/users/1# 获取/users/1.json高清秀@user.build_profile结尾# 获取/用户/新定义新@user = User.new@user.build_profile结尾# 获取/users/1/edit定义编辑@user.build_profile结尾# POST/用户# POST/users.json定义创建@user = User.new(user_params)response_to do |格式|如果@user.saveformat.html { redirect_to @user,注意:'用户已成功创建.'}format.json { 渲染:显示,状态::创建,位置:@user }别的format.html { 渲染:新}format.json { 渲染 json: @user.errors, 状态: :unprocessable_entity }结尾结尾结尾# 补丁/放置/users/1# PATCH/PUT/users/1.json定义更新response_to do |格式|如果@user.update(user_params)format.html { redirect_to @user,注意:'用户已成功更新.'}format.json { 渲染:显示,状态::ok,位置:@user }别的format.html { 渲染:编辑}format.json { 渲染 json: @user.errors, 状态: :unprocessable_entity }结尾结尾结尾# 删除/users/1# 删除/users/1.json销毁@user.destroyresponse_to do |格式|format.html { redirect_to users_url,注意:'用户已成功销毁.'}format.json { 头:no_content }结尾结尾私人的# 使用回调在动作之间共享公共设置或约束.def set_user@user = User.find(params[:id])结尾# 永远不要相信来自可怕互联网的参数,只允许白名单通过.定义用户参数params.require(:user).permit(:username, :password, :user_type_id, profile_attributes: [:id, :user_id, :first_name, :middle_name, :last_name, :phone_number, :cell_number, :email])结尾结尾############################################################################## 表单视图###########################################################################<%= form_for(@user) do |f|%><% if user.errors.any?%><div id="error_explanation"><h2><%=pluralize(user.errors.count, "error") %>禁止保存此用户:</h2><ul><% user.errors.full_messages.each do |message|%><li><%=消息%></li><%结束%><!--<li><%= debug f %></li>-->

<%结束%><div class="field"><%= f.label :username %><%= f.text_field :用户名 %>

<div class="field"><%= f.label :password %><%= f.text_field :密码%>

<div class="field"><% if params[:trainer] == "true" %><%= f.label :user_type_id %><%= f.text_field :user_type_id, :readonly =>真, :value =>'2'%><%其他%><%= f.label :user_type_id %><%= f.text_field :user_type_id, :readonly =>真, :value =>'1'%><%结束%>

<h2>帐户资料</h2><%= f.fields_for :profile do |profile|%><%#= profile.inspect %><div><%= profile.label :first_name %><%= profile.text_field :first_name %>

<div><%= profile.label :middle_name %><%= profile.text_field :middle_name %>

<div><%= profile.label :last_name %><%= profile.text_field :last_name %>

<div><%= profile.label :email %><%= profile.text_field :email %>

<div><%= profile.label :phone_number %><%= profile.telephone_field :phone_number %>

<div><%= profile.label :cell_phone %><%= profile.telephone_field :cell_number %>

<%结束%><div class="actions"><%= f.submit %>

<%=调试参数%><%=调试用户%><%=调试用户.配置文件%><%结束%>

解决方案

好吧,我在另一个问题上重新表述了这个问题,我终于找到了答案.所以我从那里粘贴我的答案,以防有人以我在这里提出问题的相同方式搜索问题.

好的,我正在回答我自己的问题,因为我知道很多人都在为此苦苦挣扎,而我实际上已经有了答案,而不是对文档的含糊回答.

首先,我们将在此示例中使用一对一关系.创建关系时,您需要确保父模型具有以下内容

  1. inverse_of:
  2. 自动保存:真
  3. accepts_nested_attributes_for :model, allow_destroy:true

这是用户模型,然后我会解释,

class User <申请记录has_one :profile, inverse_of: :user, autosave: trueaccepts_nested_attributes_for :profile, allow_destroy: true结尾

在 Rails 5 中,您需要 inverse_of:因为这告诉 Rails 存在通过外键的关系,并且需要在保存表单数据时在嵌套模型上设置.现在,如果您要从关系行中关闭 autosave: true,您将剩下 user_id 不会保存到配置文件表和其他列,除非您有验证off 然后它就不会出错,它只会在没有 user_id 的情况下保存它.这里发生的是 autosave: true 确保首先保存用户记录,以便将 user_id 存储在 的嵌套属性中个人资料模型.简而言之,这就是为什么 user_id 没有遍历到孩子,而是回滚而不是提交的原因.最后一个问题是有一些帖子告诉你在你的控制器中你应该添加 @user.build_profile 就像我在我的帖子中所说的那样.不要这样做,他们错了,在评估控制台输出后,结果是

在 2017-03-12 22:38:17 -0400 开始 GET "/users/1/edit" for 192.168.0.31无法从 192.168.0.31 渲染控制台!允许的网络:127.0.0.1、::1、127.0.0.0/127.255.255.255由 UsersController#edit 处理为 HTML参数:{"id"=>"1"}用户负载 (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1Profile Load (0.5ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 1 LIMIT 1(0.1ms) 开始SQL (0.5ms) UPDATE `profiles` SET `user_id` = NULL, `updated_at` = '2017-03-13 02:38:17' WHERE `profiles`.`id` = 1(59.5ms) 提交在布局/应用程序中呈现 users/edit.html.erb呈现的 users/_form.html.erb (44.8ms)在布局/应用程序中呈现 users/edit.html.erb (50.2ms)在 174 毫秒内完成 200 个 OK(查看次数:98.6 毫秒 | ActiveRecord:61.1 毫秒)

如果您看一下,它正在从头开始重建配置文件,并将与您正在编辑的当前用户匹配的记录的 user_id 重置为 null.所以要非常小心,因为我已经看到很多帖子提出了这个建议,我花了几天的时间来寻找解决方案!

Ok folks, Rails 5 has really had its nuances differing from Rails 4. What I have going on is that every time I click the submit button on the form it reloads with the error Profile user must exist and Profile user can't be blank. The form loads fine including the nested models form, but for what ever reason it is failing to save the parent model before attempting to save the child model with the following output to the console:

Puma starting in single mode...
* Version 3.7.0 (ruby 2.2.6-p396), codename: Snowy Sagebrush
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
Started POST "/users" for 192.168.0.31 at 2017-03-09 18:51:04 -0500
Cannot render console from 192.168.0.31! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
  ActiveRecord::SchemaMigration Load (0.2ms)  SELECT `schema_migrations`.* FROM `schema_migrations`
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"JPKO+ppAYqwWS8tWeXhEtbUWynXREu9jYlF0KIlyPgUaabHSzjPZocSxCvr/WEm1r6wAQyT1CvA6hNkZWfPD3Q==", "user"=>{"username"=>"test", "password"=>"[FILTERED]", "user_type_id"=>"1", "profile_attributes"=>{"first_name"=>"123", "middle_name"=>"123", "last_name"=>"123", "email"=>"123@123.com", "phone_number"=>"1234567890", "cell_number"=>"1234567890"}}, "commit"=>"Create User"}
   (0.1ms)  BEGIN
   (0.2ms)  ROLLBACK
  Rendering users/new.html.erb within layouts/application
  Rendered users/_form.html.erb (112.5ms)
  Rendered users/new.html.erb within layouts/application (118.7ms)
Completed 200 OK in 834ms (Views: 780.1ms | ActiveRecord: 2.2ms)

I have have had other problems out of this relationship and I am thinking that maybe I need to rebuild the project. Here is all of the relevant code around this issue:

###############################################################################
### Users Model
###############################################################################
    class User < ApplicationRecord
      has_one :profile, inverse_of: :user
      accepts_nested_attributes_for :profile, allow_destroy: true
    end

###############################################################################
### Profile Model
###############################################################################
    class Profile < ApplicationRecord
      belongs_to :user, inverse_of: :profile
      validates_presence_of :user
    end
###############################################################################
### Users Controller
###############################################################################
    class UsersController < ApplicationController
      before_action :set_user, only: [:show, :edit, :update, :destroy]

      # GET /users
      # GET /users.json
      def index
        @users = User.all
      end

      # GET /users/1
      # GET /users/1.json
      def show
        @user.build_profile
      end

      # GET /users/new
      def new
        @user = User.new
        @user.build_profile
      end

      # GET /users/1/edit
      def edit
        @user.build_profile
      end

      # POST /users
      # POST /users.json
      def create
        @user = User.new(user_params)

        respond_to do |format|
          if @user.save
            format.html { redirect_to @user, notice: 'User was successfully created.' }
            format.json { render :show, status: :created, location: @user }
          else
            format.html { render :new }
            format.json { render json: @user.errors, status: :unprocessable_entity }
          end
        end
      end

      # PATCH/PUT /users/1
      # PATCH/PUT /users/1.json
      def update
        respond_to do |format|
          if @user.update(user_params)
            format.html { redirect_to @user, notice: 'User was successfully updated.' }
            format.json { render :show, status: :ok, location: @user }
          else
            format.html { render :edit }
            format.json { render json: @user.errors, status: :unprocessable_entity }
          end
        end
      end

      # DELETE /users/1
      # DELETE /users/1.json
      def destroy
        @user.destroy
        respond_to do |format|
          format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
          format.json { head :no_content }
        end
      end

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

        # Never trust parameters from the scary internet, only allow the white list through.
        def user_params
          params.require(:user).permit(:username, :password, :user_type_id, profile_attributes: [:id, :user_id, :first_name, :middle_name, :last_name, :phone_number, :cell_number, :email])
        end
    end

###############################################################################
### Form View
###############################################################################
    <%= form_for(@user) do |f| %>
      <% if user.errors.any? %>
        <div id="error_explanation">
          <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

          <ul>
          <% user.errors.full_messages.each do |message| %>
            <li><%= message %></li>
          <% end %>
            <!--<li><%= debug f %></li>-->
          </ul>
        </div>
      <% end %>

      <div class="field">
        <%= f.label :username %>
        <%= f.text_field :username %>
      </div>

      <div class="field">
        <%= f.label :password %>
        <%= f.text_field :password %>
      </div>

      <div class="field">
        <% if params[:trainer] == "true" %>
          <%= f.label :user_type_id %>
          <%= f.text_field :user_type_id, :readonly => true, :value => '2' %>
        <% else %>
          <%= f.label :user_type_id %>
          <%= f.text_field :user_type_id, :readonly => true, :value => '1'  %>
        <% end %>
      </div>
        <h2>Account Profile</h2>
        <%= f.fields_for :profile do |profile| %>
          <%#= profile.inspect %>
            <div>
              <%= profile.label :first_name %>
              <%= profile.text_field :first_name %>
            </div>
            <div>
              <%= profile.label :middle_name %>
              <%= profile.text_field :middle_name %>
            </div>
            <div>
              <%= profile.label :last_name %>
              <%= profile.text_field :last_name %>
            </div>
            <div>
              <%= profile.label :email %>
              <%= profile.text_field :email %>
            </div>
            <div>
              <%= profile.label :phone_number %>
              <%= profile.telephone_field :phone_number %>
            </div>
            <div>
              <%= profile.label :cell_phone %>
              <%= profile.telephone_field :cell_number %>
            </div>
        <% end %>
      <div class="actions">
        <%= f.submit %>
      </div>
        <%= debug params %>
        <%= debug user %>
        <%= debug user.profile %>
    <% end %>

解决方案

Alright I rephrased the question on another question and I finally found the answer to this. So I am pasting my answer from there, in case someone searches for the issue in the same fashion that I was asking the question here.

Ok, I am answering my own question because I know many people are struggling with this and I actually have the answer and not a vague response to the documentation.

First we will just be using a one to one relationship for this example. When you create your relationships you need to make sure that the parent model has the following

  1. inverse_of:
  2. autosave: true
  3. accepts_nested_attributes_for :model, allow_destroy:true

Here is the Users model then I will explain,

class User < ApplicationRecord
  has_one :profile, inverse_of: :user, autosave: true
  accepts_nested_attributes_for :profile, allow_destroy: true
end

in Rails 5 you need inverse_of: because this tells Rails that there is a relationship through foreign key and that it needs to be set on the nested model when saving your form data. Now if you were to leave autosave: true off from the relationship line you are left with the user_id not saving to the profiles table and just the other columns, unless you have validations off and then it won't error out it will just save it without the user_id. What is going on here is autosave: true is making sure that the user record is saved first so that it has the user_id to store in the nested attributes for the profile model. That is it in a nutshell why the user_id was not traversing to the child and it was rolling back rather than committing. Also one last gotcha is there are some posts out there telling you in your controller for the edit route you should add @user.build_profile like I have in my post. DO NOT DO IT THEY ARE DEAD WRONG, after assessing the console output it results in

Started GET "/users/1/edit" for 192.168.0.31 at 2017-03-12 22:38:17 -0400
Cannot render console from 192.168.0.31! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#edit as HTML
  Parameters: {"id"=>"1"}
  User Load (0.4ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Profile Load (0.5ms)  SELECT  `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 1 LIMIT 1
   (0.1ms)  BEGIN
  SQL (0.5ms)  UPDATE `profiles` SET `user_id` = NULL, `updated_at` = '2017-03-13 02:38:17' WHERE `profiles`.`id` = 1
   (59.5ms)  COMMIT
  Rendering users/edit.html.erb within layouts/application
  Rendered users/_form.html.erb (44.8ms)
  Rendered users/edit.html.erb within layouts/application (50.2ms)
Completed 200 OK in 174ms (Views: 98.6ms | ActiveRecord: 61.1ms)

If you look it is rebuilding the profile from scratch and resetting the user_id to null for the record that matches the current user you are editing. So be very careful of this as I have seen tons of posts making this suggestion and it cost me DAYS of research to find a solution!

这篇关于Rails 5 - 保存回滚,因为嵌套模型父模型没有在子模型之前保存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆