多模型保存,如何包装事务和报告错误

Bla*_*man 9 validation ruby-on-rails

我有一个注册表单模型,在注册期间接受用户输入:

class RegForm
    include ActiveModel::Model
    include ActiveModel::Validations

    attr_accessor :company_name, :email, :password
    validates_presence_of # ...

end
Run Code Online (Sandbox Code Playgroud)

在此注册过程中,我有多个需要创建的模型,我不确定如何正确显示错误消息以及如何将模型错误消息冒泡回UI.

if @reg_form.valid?
   account = Account.create!(@reg_form)
else 
...
Run Code Online (Sandbox Code Playgroud)

Account.create!好像:

def self.create!(reg_form)
  account = Account.create_from_reg!(reg_form)
  location = location.create_from_reg!(account, reg_form)
  ..
  ..
  account.location = location
  ..
  account.save!

  account
end
Run Code Online (Sandbox Code Playgroud)
  1. 所以我很困惑如何显示所有这些节省的模型的错误消息
  2. 如果reg_form没有所有其他模型的正确数据,如何显示或失败验证.
  3. 如何确保将其包含在事务中,因此如果任何模型在注册期间无法保存,我不会保存任何内容.

Ash*_*aka 5

让我们从头开始。

我们希望我们的注册表单对象具有与任何其他 ActiveRecord 模型相同的 API:

// view.html
<%= form_for(@book) do |f| %>
<% end %>

# controller.rb
def create
  @book = Book.new(book_params)

  if @book.save
    redirect_to @book, notice: 'Book was successfully created.'
  else
    render :new
  end
end
Run Code Online (Sandbox Code Playgroud)

为此,我们创建以下对象:

class RegForm
  include ActiveModel::Model

  attr_accessor :company_name, :email, :password

  def save
    # Save Location and Account here
  end
end
Run Code Online (Sandbox Code Playgroud)

现在,通过包含ActiveModel::Model,我们RegForm获得了大量功能,包括显示错误和验证属性(是的,没有必要包含ActiveModel::Validations)。在下一步中,我们添加一些验证:

validates :email, :password, presence: true
Run Code Online (Sandbox Code Playgroud)

我们进行更改save运行这些验证:

def save
  validate
  # Save Location and Account here
end
Run Code Online (Sandbox Code Playgroud)

validatetrue如果所有验证都通过false则返回,否则返回。

validate还增加了错误@reg_form(所有的ActiveRecord模型有一个errors当验证失败其中填充哈希)。这意味着在视图中我们可以执行以下任何操作:

@reg_form.errors.messages
#=> { email: ["can't be blank"], password: ["can't be blank"] }

@reg_form.errors.full_messages
#=> ["Email can't be blank", "Password can't be blank"]

@reg_form.errors[:email]
#=> ["can't be blank"]

@reg_form.errors.full_messages_for(:email)
#=> ["Email can't be blank"]
Run Code Online (Sandbox Code Playgroud)

同时,我们RegistrationsController应该看起来像这样:

def create
  @reg_form = RegForm.new(reg_params)

  if @reg_form.save
    redirect_to @reg_form, notice: 'Registration was successful'
  else
    render :new
  end
end
Run Code Online (Sandbox Code Playgroud)

我们可以清楚地看到,当@reg_form.save返回时falsenew视图被重新渲染。

最后,我们进行更改,save以便我们的模型save调用包含在事务中

def save
  if valid?
    ActiveRecord::Base.transaction do
      location = Location.create!(location_params)
      account = location.create_account!(account_params)
    end
    true
  end
rescue ActiveRecord::StatementInvalid => e
  # Handle database exceptions not covered by validations.
  # e.message and e.cause.message can help you figure out what happened
end
Run Code Online (Sandbox Code Playgroud)

值得注意的点:

  1. create!用于代替create. 该交易是否将引发异常只回滚(它伴随着一声巨响方法通常做的)。

  2. validate只是 的别名valid?。为了避免所有的缩进,我们可以在save方法的顶部使用一个保护:

    return if invalid?
    
    Run Code Online (Sandbox Code Playgroud)
  3. 我们可以通过执行以下操作将数据库异常(如电子邮件唯一性约束)转换为错误:

    rescue ActiveRecord::RecordNotUnique
      errors.add(:email, :taken)
    end
    
    Run Code Online (Sandbox Code Playgroud)
  4. 我们可以使用符号添加与属性不直接关联的错误:base

    errors.add(:base, 'Company and Email do not match')
    
    Run Code Online (Sandbox Code Playgroud)