有没有办法避免在分配集合属性时自动保存对象(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>
请帮我解决这个问题。
答案 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
,因为替换会产生更好的查询(或根本不需要转到数据库)。