为什么在Rails 4中提交包含未更改数据的表单时会调用更新操作?

时间:2016-06-15 12:02:25

标签: ruby-on-rails ruby-on-rails-4

我将两个表中的数据加载到单个表单中。在表单上,​​我为模型B(孩子)创建新记录,而不为模型A(父母)更改任何内容。如果对于模型B,输入错误的内容(例如,没有' .com'的电子邮件地址)则会显示模型B的验证错误。纠正问题(输入正确的电子邮件)并按“提交”按钮后,将调用模型A的更新操作。但在模型A中没有变化!为什么在这种情况下调用模型A的更新操作?没有数据可以更新到数据库。

以下是更详细的解释:

我使用一个注册表单输入两个模型的数据:租户和用户。任何租户都会有很多用户。当新用户想要注册时,他还会输入有关其租户(公司)的数据。如果输入的租户不存在,则会与新用户一起创建新租户。这部分运作良好。问题是当新用户输入数据库中已存在的租户名称时,用户输入错误信息(例如,错误的电子邮件格式)。如果输入错误,则验证错误(例如,电子邮件不能为空)出现 AND 同一租户的所有先前保存的用户也会显示为render 'new'的结果在TenantController中。

要仅显示触发验证错误的新记录,我在注册表单中添加了<% if u.object.new_record? %>(也许这不是正确的方法吗?):

  <%= f.fields_for(:users) do |u| %>
    <% if u.object.new_record? %>
    ...
    <% end %>
  <% end %>

在这种情况下,正确显示注册表单,,当我更正验证错误的原因(例如,输入正确的电子邮件)并按保存按钮,然后执行更新操作而不是创造行动。这就是问题,因为我不希望任何用户能够在注册阶段更新租户数据。在注册时,用户应该只能创建他的个人数据,而不能更新他的租户数据。请建议如何做到这一点。

租户模式:

class Tenant < ActiveRecord::Base

  has_many :users, dependent: :destroy, inverse_of: :tenant
  accepts_nested_attributes_for :users    

  before_validation do 
    self.status = 0 
    self.name = name_orig.upcase 
    email.downcase!
  end

  validates :name_orig, presence: true, length: { maximum: 255 }

  validates :name, uniqueness: { case_sensitive: false }

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }

  validates :status, presence: true

end

用户模型:

class User < ActiveRecord::Base

  belongs_to :tenant, inverse_of: :users

  before_validation do 
    self.status = 0
    self.email = email.downcase
  end

  validates :tenant, presence: true

  VALID_USERNAME_REGEX = /\A\w+\s?\w*\z/i
  validates :name,  presence: true, length: { maximum: 50 },
                    format: { with: VALID_USERNAME_REGEX },
                    uniqueness: { case_sensitive: false }

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }

  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  validates :status, presence: true

end

租户控制人:

class TenantsController < ApplicationController

  def show
    @tenant = Tenant.find(params[:id])
  end

  def new
    @tenant = Tenant.new
    @tenant.users.build
  end

  def create
    @tenant = Tenant.find_by(name: tenant_params[:name_orig].upcase)
    if @tenant == nil
      @tenant = Tenant.new(name_orig: tenant_params[:name_orig], 
                           email: tenant_params[:email])
      @user = @tenant.users.build(tenant_params[:users_attributes]["0"])
      @tenant.save
      if @tenant.save
        flash[:success] = "Welcome!"
        redirect_to @user   # redirects to user profile
      else
        render 'new'
      end
    else
      @user = @tenant.users.build(tenant_params[:users_attributes]["0"])
      if @user.save
        flash[:success] = "Welcome!"
        redirect_to @user   # redirects to user profile
      else
        render 'new'
      end
    end
  end   

  private

    def tenant_params
      params.require(:tenant).permit(:name_orig, :email,
          users_attributes: [:name, :email, :password, :password_confirmation])
    end
end

注册表单:

<%= form_for(@tenant) do |f| %>

  <%= render 'shared/tenant_error_messages' %>

  <%= f.label :name_orig, "Company name" %>
  <%= f.text_field :name_orig, class: 'form-control' %>

  <%= f.label :email, "Company e-mail" %>
  <%= f.email_field :email, class: 'form-control' %>

  <%= f.fields_for(:users) do |u| %>
    <% if u.object.new_record? %>
      <%= u.label :name, "User name" %>
      <%= u.text_field :name, class: 'form-control' %>

      <%= u.label :email, "User e-mail" %>
      <%= u.email_field :email, class: 'form-control' %>

      <%= u.label :password, "Password" %>
      <%= u.password_field :password, class: 'form-control' %>

      <%= u.label :password_confirmation, "Password confirmation" %>
      <%= u.password_field :password_confirmation, class: 'form-control' %>
    <% end %>
  <% end %>

  <%= f.submit "Save", class: "btn btn-primary" %>
<% end %>

1 个答案:

答案 0 :(得分:0)

让我先回答一下为什么你的代码没有按预期工作。

创建操作下的行

  

@tenant = Tenant.find_by(name:tenant_params [:name_orig] .upcase)

分配已存在于数据库中的租户对象。因此,当您将@tenant对象提供给form_for(@tenant)时,操作将自动转换为更新而不是创建。这就是为什么你从来没有遇到过新租户创造的任何问题。

解决方案是明确设置form_for url:

  

= form_for(@tenant,as :: tenant,url:tenant_path(@tenant),method :: post)

这总是会向创建操作发送一个帖子请求。

我对改进设计的两分钱

此外,如果在任何时间点只有一个用户和一个租户将通过表单提交,那么您可以在表单中使用User作为父级而不是Tenant(因为User对象是主要对象,如果我是对的,则以该表格提交。你可以想到像

这样的逆转
    = form_for(@user) do |f|
      = # Fields for user goes here
      = f.fields_for(@tenant) do |t|
        = # Fields for tenant goes here

这种方法的优点是,即使租户已经存在,您也可以在表单中使用该id作为tenant_id。当然,您需要将控制器更改为UserController和其他控制器代码。希望这会有所帮助。

PS:使用Haml代替Erb道歉。