active_record-acts_as - 验证子模型上的父字段

时间:2015-03-12 14:30:27

标签: ruby-on-rails-4

我使用的是Rails 4.2.0和active_record-acts_as gem。

此gem模拟ActiveRecord模型的多表继承。

我的父模型名为附件,包含子模型规范发布

class Attachment < ActiveRecord::Base
  actable
end

class Specification < ActiveRecord::Base
  acts_as :attachment
end

class Release < ActiveRecord::Base
  acts_as :attachment
end

我的附件模型包含字段nameactable_idactable_type(由acts_as gem使用)和标准回形针字段。

规范发布有多个特定于其类型的字段(因此我认为它们不适合单表继承)。

我要做的是验证子模型上的name而不是父模型,因为不同的规则适用于发布规范

在线验证似乎工作正常:

class Specification < ActiveRecord::Base
  acts_as :attachment

  validates :name, presence: true
end

但是当我尝试这样的事情时:

class Specification < ActiveRecord::Base
  acts_as :attachment

  validates :name, presence: true, uniqueness: { case_sensitive: true }
end

调用.valid时出现以下错误?

NoMethodError: undefined method `limit' for nil:NilClass

我写了一些适用于儿童模型的自定义验证,但我希望我不会这样做。

验证孩子的主要原因是因为我使用以下内容来获取更简洁的错误消息,具体取决于模型(规范,发布):

class Specification < ActiveRecord::Base
  ...

  HUMANIZED_ATTRIBUTES = {
    name: "Version"
  }

  def self.human_attribute_name(attr, options={})
    HUMANIZED_ATTRIBUTES[attr.to_sym] || super
  end
end

所以它返回的内容如下:

Version can't be blank.代替Name can't be blank.

我还尝试使用以下内容验证附件(父级):

with_options if: Proc.new { |x| x.actable_type == "Specification" } do |s|
  s.validates :name, presence: true, uniqueness: { case_sensitive: true }
end

但是我没有收到我想要的错误消息。 Name代替Version

我可能大肆过分复杂化。有什么想法吗?

2 个答案:

答案 0 :(得分:1)

只是总结一下上述评论中针对遭遇类似错误的讨论:


问题在于db中没有specifications.name列。

当您验证状态时,它仅检查属性,当您检查它在此列的数据库中搜索的唯一性时。

答案 1 :(得分:1)

正如Evgeny Petrov所做的那样,无法在子模型上验证父属性(可能可以使用自定义验证器完成)。

最后,我选择使用with_options if:验证父模型,以定位某些actable_type

为了理清一些自定义错误消息,我将子类的HUMANIZED_ATTRIBUTES哈希与父级的self.human_attribute_name结合起来。

以下是一个例子:

class Attachment < ActiveRecord::Base
  actable

  before_validation :set_humanized_attributes

  class << self; attr_reader :humanized_attributes end
  @humanized_attributes = {}

  with_options if: proc { |x| x.actable_type == 'Release' do |s|
    s.validates :name, presence: true, uniqueness: { case_sensitive: false }
  end

  with_options if: proc { |x| x.actable_type == 'Specification' do |s|
    s.validates :name, presence: true, uniqueness: { case_sensitive: true }
  end

  def self.human_attribute_name(attr, options = {})
    humanized_attributes[attr.to_sym] || super
  end

  private

  def set_humanized_attributes
    @humanized_attributes = actable_type.constantize::HUMANIZED_ATTRIBUTES
  end
end

class Specification < ActiveRecord::Base
  acts_as :attachment
  HUMANIZED_ATTRIBUTES = {
    name: 'Version'
  }

  # child attribute validations here
end

class Release < ActiveRecord::Base
  acts_as :attachment
  HUMANIZED_ATTRIBUTES = {
    name: 'Release'
  }

  # child attribute validations here
end

在rails控制台中:

> s = Specification.new
=> #<Specification id ... >
> s.valid?
=> false
> s.errors.full_messages
=> ["Version can't be blank"]
> r = Release.new
=> #<Release id ... >
> r.valid?
=> false
> r.errors.full_messages
=> ["Release can't be blank"]

需要检查一些边缘情况并使其更加健壮,但是现在这实现了我的要求。