通过关联获取带有has_many的ActiveRecord :: RecordInvalid错误;连接表上的验证问题

时间:2013-12-15 20:55:10

标签: ruby-on-rails ruby ruby-on-rails-3 activerecord has-many-through

我有三个相关的模型:

class Product < ActiveRecord::Base
  belongs_to :user
  has_many :descriptions, {
    dependent: :destroy,
    before_add: [:add_user_id_to_description, :validate_description]
  }
  has_many :documents, through: :descriptions

  # ...

  def validate_description(d)
    unless d.valid?
      d.errors[:user_id].each do |err|
        self.errors.add(:base, "Doc error: #{err}")
      end
    end
  end
end

class Document < ActiveRecord::Base
  belongs_to :user
  has_many :descriptions, {
    dependent: :destroy,
    before_add: [:add_user_id_to_description, :validate_description]
  }
  has_many :products, through: :descriptions
end

class Description < ActiveRecord::Base
  belongs_to :user
  belongs_to :product
  belongs_to :document
end

当我做类似的事情时:

doc = user.documents.build
doc.update_attributes(:product_ids => [1,2])

description验证失败,然后我false获得了docdoc上的相应错误。这正是我想要的。

但是,如果doc = user.documents.first doc.update_attributes(:product_ids => [1,2]) 已经存在,例如:

description

ActiveRecord::RecordInvalid验证失败,然后我收到insert_record错误。

我确切知道为什么会发生这种情况 - 来自has_many_through_association.rbsave!方法在内部调用save!,这会传播错误。它会提前退出,跳过此调用,以获取新记录。

我是否可以通过某种方式设置模型来阻止此rescue?或者我从错误中被迫validates_associated :descriptions

修改

我已经尝试了下面Carlos Drew描述的设置;我还尝试设置inverse_of: :whatever,并将has_many :descriptions添加到before_validation选项哈希中。我还尝试在ProductDocument模型上设置Document Load (1.8ms) SELECT "documents".* FROM "documents" WHERE "documents"."user_id" = 19 ORDER BY "documents"."id" DESC LIMIT 1 (1.0ms) BEGIN Product Load (41.7ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT 1 [["id", 3640]] Product Load (4.1ms) SELECT "products".* FROM "products" INNER JOIN "descriptions" ON "products"."id" = "descriptions"."product_id" WHERE "descriptions"."document_id" = 3552 User Load (7.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 19 LIMIT 1 Account Load (2.0ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."user_id" = 19 LIMIT 1 (0.9ms) SELECT COUNT(*) FROM "descriptions" WHERE "descriptions"."user_id" = 19 (1.2ms) ROLLBACK ActiveRecord::RecordInvalid: Validation failed: User You have reached limit of 1 from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/validations.rb:56:in `save!' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/attribute_methods/dirty.rb:33:in `save!' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/transactions.rb:264:in `block in save!' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/transactions.rb:313:in `block in with_transaction_returning_status' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract/database_statements.rb:192:in `transaction' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/transactions.rb:208:in `transaction' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/transactions.rb:311:in `with_transaction_returning_status' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/transactions.rb:264:in `save!' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/has_many_through_association.rb:85:in `save_through_record' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/has_many_through_association.rb:52:in `insert_record' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/collection_association.rb:496:in `block (2 levels) in concat_records' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/collection_association.rb:344:in `add_to_target' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/collection_association.rb:495:in `block in concat_records' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/collection_association.rb:493:in `each' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/collection_association.rb:493:in `concat_records' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/collection_association.rb:134:in `block in concat' ... 14 levels... from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/associations/builder/collection_association.rb:71:in `block in define_writers' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/attribute_assignment.rb:85:in `block in assign_attributes' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/attribute_assignment.rb:78:in `each' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/attribute_assignment.rb:78:in `assign_attributes' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/persistence.rb:216:in `block in update_attributes' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/transactions.rb:313:in `block in with_transaction_returning_status' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract/database_statements.rb:192:in `transaction' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/transactions.rb:208:in `transaction' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/transactions.rb:311:in `with_transaction_returning_status' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.13/lib/active_record/persistence.rb:215:in `update_attributes' from (irb):2 from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.13/lib/rails/commands/console.rb:47:in `start' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.13/lib/rails/commands/console.rb:8:in `start' from /usr/local/rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.13/lib/rails/commands.rb:41:in `<top (required)>' from script/rails:6:in `require' from script/rails:6:in `<main>' 回调,但显然关联回调首先运行(?)。每次尝试似乎都会产生完全相同的错误消息。

我正在从下面的控制台粘贴我的错误跟踪。

{{1}}

1 个答案:

答案 0 :(得分:2)

我的直觉是你用before_add: :validate_description过度设计了模型的验证。你没有标准的Rails / ActiveRecord方法和约定吗?具体而言,可以为关联模型之间的验证处理设置validates: true

尽管如此,关联验证仍有一些问题,我建议您阅读以下内容:

修改

我对此感到非常好奇,然后通过规格(并且它在public github project中)来复制问题,正如您所描述的那样。我仍然认为手册before_add验证是过度设计的,我没有使用它们,但我遇到了你描述的问题。

所以,我想要了解的是你所遇到的是否是预期和期望的。如果没有自以为是的话,Rails就没有任何意义,并且可能使用has_many-through关联的直接设置是一种编码器提防的用例。要清楚,你正在做的事情有点奇怪:当你要求设置document.product_ids时,你实际在做的是在某些描述对象上设置匹配的document_id和product_id。对?这很奇怪,并且在意图/预期结果中非常不清楚。

那么替代方法是什么?您正在做的是向文档添​​加描述,这些描述在产品上。那么,为什么不通过描述界面与文档产品进行交互呢?我认为这应该避免has_many-through setter怪异,并且提供者更清晰,界面。