当使用collection_singular_ids = ids方法时,Rails 3不能对持久对象执行验证

时间:2014-10-28 15:18:19

标签: ruby-on-rails-3 has-many-through model-associations

有没有办法避免在分配集合属性时自动保存对象(collection_singular_ids = ids方法)?

例如,我有以下测试和包模型,包有很多测试。用户可以使用多个测试来构建包捆绑包。

# test.rb
class Test < ActiveRecord::Base
end

# package.rb
class Package < ActiveRecord::Base
  has_many :package_tests 
  has_many :tests, :through => :package_tests
  belongs_to :category

  validate :at_most_3_tests

  private
  # tests count will differ depends on category.
  def at_most_3_tests
    errors.add(:base, 'This package should have at most three tests') if  test_ids.count > 3
  end
end

# package_test.rb
class PackageTest < ActiveRecord::Base
  belongs_to :package
  belongs_to :test

  validates_associated :package
end

当包对象时验证没有问题。

1.9.2 :001> package = Package.new(:name => "sample", :cost => 3.3, :test_ids => [1,2,3,4])
=> #<Package id: nil, name: "sample", cost: 3.3> 
1.9.2 :002> package.test_ids
=> [1, 2, 3, 4] 
1.9.2 :003> package.save
=> false 
1.9.2 :004> package.save!
ActiveRecord::RecordInvalid: Validation failed: This package should have at most three tests
1.9.2: 005> package.test_ids = [1,2]
=> [1, 2] 
1.9.2 :005> package.save!
=> true

但我无法使用持久包对象来点击at_most_3_tests方法。

分配测试ID时立即创建连接表记录

1.9.2: 006> package
=> #<Package id: 1, name: "sample", cost: 3.3> 
1.9.2: 007> package.test_ids
=> [1,2]
1.9.2: 007> package.test_ids = [1,2,3,4,5]
=> [1,2,3,4,5]
1.9.2: 008> package.test_ids 
=> [1,2,3,4,5]

客户端要求是下拉界面,用于选择包形式的多个测试 并使用select2 jquery插件进行下拉。 Rhmtl代码看起来像

<%= form_for @package do |f| %>
  <%= f.text_field :name %>
  <div> <label>Select Tests</label> </div>
  <div>
    <%= f.select "test_ids", options_for_select(@tests, f.object.test_ids), {}, { "data-validate" => true, :multiple => true} %>
  </div>
   

请帮我解决这个问题。

2 个答案:

答案 0 :(得分:5)

限制关联数

您可以使用以下验证,而不是您的方法:

has_many :tests, :length => { :maximum => 3 }

使用多重选择

之前我遇到过这个问题,我使用以下代码解决了这个问题:

<%= f.select(:test_ids, options_from_collection_for_select(@tests, :id, :name,  @promotion.test_ids), {}, {multiple: true, "data-validate" => true}) =>

我认为options_from_collection_for_select,从此link阅读帖子示例的类别可能对您有所帮助。

验证

我使用了validates_associated,如下所示:

 validates_associated :tests

获取持久对象的旧属性

您可以使用reload作为有效记录,如下所示:

1.9.2: 006> package
=> #<Package id: 1, name: "sample", cost: 3.3> 
1.9.2: 007> package.test_ids
=> [1,2]
1.9.2: 007> package.test_ids = [1,2,3,4,5]
=> [1,2,3,4,5]
1.9.2: 007> package.reload
=> #<Package id: 1, name: "sample", cost: 3.3> 
1.9.2: 008> package.test_ids 
=> [1,2]

或者您可以检查包对象的验证,如果是false则重新加载它:

unless package.valid?
  package.reload
end

答案 1 :(得分:3)

如果您在控制器中手动分配test_ids,我建议使用嵌套属性更新整个对象。这假设params[:package][:test_ids]设置为您的测试ID列表(Mohamed的回答将有助于)。 所以你的控制器动作看起来像这样:

def update
  package = Package.find(params[:id])
  package.update_attributes params[:package]
end

这将在ActiveRecord /数据库事务中一次更新所有内容。这意味着如果验证失败,所有更改都将被回滚,因此测试得到保存并不重要。更多信息是here

另外,我建议调用tests.size而不是test_ids.count,因为替换会产生更好的查询(或根本不需要转到数据库)。