验证ActiveRecord中的关联模型(has_many through)

时间:2013-08-21 18:10:01

标签: ruby-on-rails ruby activerecord

我希望能够与关联的多对多实体一起创建和编辑模型。 应通过具有附加字段和完整性验证(例如唯一性或存在性)的模型来连接相关对象。 我想用Rails提供的标准验证机制来检查完整性。

一个示例是Post(类似于Stackoverflow),至少有一个Tag且不超过3 Tags。无法将Tag多次分配到单个Post。 此外,标签将被订购(联接表中的priority)。

模型应使用带validates的自定义验证器,并在违反任何约束时向Post#errors添加错误消息。 在编辑时,如果任何验证失败(例如删除了帖子标题或添加了太多标签),模型及其与TagTagAssignment)的关系将不会被保存。

示例模型:

class Post < ActiveRecord::Base
  has_many :tag_assignments
  has_many :tags, :through => :tag_assignments

  validates :title,
            presence: true
  validates :content,
            presence: true
  validates :number_of_tags

  def number_of_tags
    valid = self.tags.count.in? 1..3
    errors.add('error: 1 <= tag.size <= 3') unless valid
    valid
  end
end

class Tag < ActiveRecord::Base
  has_many :tag_assignments
  has_many :posts, through: :tag_assignment

  validates :name,
            presence: true
end

class TagAssignment < ActiveRecord::Base
  belongs_to :user
  belongs_to :tag

  validates :tag_id,
            uniqueness: {scope: :post_id}
  validates :priority,
            presence: true,
            uniqueness: {scope: :tag_id}
end

使用示例:

post = Post.build(title: 'title', content: 'content')
post.save # false
post.errors # contains message: 'error: 1 <= tag.size <= 3'
post.tagger << Tag.first
post.save # true
post_id = post.id

post = Post.find(post_id)
post.tagger << Tag.all[5..10]
post.save # false
          # but tag_assignments were already created
          # and old TagAssignments were not destroyed

(在示例中,我假设tagger是使用set TagAssignments构建priority的方法

管理habtm关系并从内置验证系统中受益的最佳设计模式是什么?

1 个答案:

答案 0 :(得分:0)

我需要的解决方案是使用.build代替<<

post = Post.build(title: 'title', content: 'content')
post.tag_assignments.build(tag: Tag.first, priority: 1)
post.save # true, TagAssignments are persisted here
post_id = post.id

在替换旧的作业时仍然存在孤儿的问题,对我来说仍未解决:

post.tag_assignments = [TagAssignment.new(tag: Tag.first, priority: 1)]  # an update on assignments sets user_id = NULL and new assignment is inserted
post.title = nil
post.valid? # false, but correct TagAssignments were replaced with new ones