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

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

class RegForm include ActiveModel::Model include ActiveModel::Validations attr_accessor :company_name, :email, :password validates_presence_of # ... end 

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

 if @reg_form.valid? account = Account.create!(@reg_form) else ... 

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 
  1. 所以我很困惑如何显示所有这些节省的模型的错误消息
  2. 如果reg_form没有所有其他模型的正确数据,如何显示或失败validation。
  3. 如何确保将其包含在事务中,因此如果在注册期间任何模型无法保存,我不会保存任何内容。

让我们从头开始。

我们希望我们的注册表单对象具有与任何其他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 

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

 class RegForm include ActiveModel::Model attr_accessor :company_name, :email, :password def save # Save Location and Account here end end 

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

 validates :email, :password, presence: true 

我们更改save以便它运行这些validation:

 def save validate # Save Location and Account here end 

如果所有validation都通过,则validate返回true ,否则返回false

validate还会向@reg_form添加错误 (所有ActiveRecord模型都有一个errors哈希值,在validation失败时填充)。 这意味着在视图中我们可以执行以下任何操作:

 @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"] 

同时,我们的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 

我们可以清楚地看到,当@reg_form.save返回false ,将重新呈现new视图。

最后,我们更改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 

值得注意的要点:

  1. create! 用来代替create 。 只有在引发exception时才会回滚事务 (通常会发生爆炸的方法)。

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

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

     rescue ActiveRecord::RecordNotUnique errors.add(:email, :taken) end 
  4. 我们可以使用符号:base来添加与属性没有直接关联的错误:

     errors.add(:base, 'Company and Email do not match') 

我认为交易和error handling将帮助您解决问题。

 def save_or_rescue ActiveRecord::Base.transaction do account = Account.create_from_reg!(reg_form) location = location.create_from_reg!(account, reg_form) ... end rescue ActiveRecord::RecordInvalid => exception puts exception end