【问题标题】:The perfect way to validate and test Rails 3 associations (using RSpec/Remarkable)?验证和测试 Rails 3 关联的完美方式(使用 RSpec/Remarkable)?
【发布时间】:2012-07-04 10:36:55
【问题描述】:

我对在 Rails 3 中进行测试还是很陌生,我使用 RSpec 和 Remarkable。我已经阅读了很多帖子和一些书籍,但我仍然不确定何时使用该协会的名称,何时使用其 ID。

class Project < ActiveRecord::Base
  has_many :tasks
end

class Task < ActiveRecord::Base
  belongs_to :project
end

由于良好的做法,我想保护我的属性免受批量分配:

class Task < ActiveRecord::Base
  attr_accessible :project  # Or is it :project_id??

  belongs_to :project
end

首先,我想确保一个项目永远不会在没有有效任务的情况下存在:

class Task < ActiveRecord::Base
  validates :project, :presence => true      # Which one is the...
  validates :project_id, :presence => true   # ...right way to go??
end

我还想确保分配的项目或项目 ID 始终有效:

class Task < ActiveRecord::Base
  validates :project, :associated => true     # Again, which one is...
  validates :project_id, :associated => true  # ...the right way to go?
end

...当我使用 :associated 时是否需要对 :presence 进行验证??

非常感谢您的澄清,似乎经过数小时的阅读和尝试使用 RSpec/Shoulda/Remarkable 测试东西后,由于所有的树木,我再也看不到森林了...

【问题讨论】:

  • 好清晰的问题。确认一下,您不是说要“确保任务永远在没有有效(父)项目的情况下存在”吗?

标签: ruby-on-rails validation


【解决方案1】:

这似乎是正确的做法:

attr_accessible :project_id

您也不必将:project 放在那里!无论如何都可以做到task.project=(Project.first!)

然后使用以下命令检查:project_id是否存在(使用task.project=(...)时也会设置:project_id):

validates :project_id, :presence => true

现在确保关联的项目像这样有效:

validates :project, :associated => true

所以:

t = Task.new
t.project_id = 1 # Value is accepted, regardless whether there is a Project with ID 1
t.project = Project.first # Any existing valid project is accepted
t.project = Project.new(:name => 'valid value') # A new valid project is accepted
t.project = Project.new(:name => 'invalid value') # A new invalid (or an existing invalid) project is NOT accepted!

有点遗憾的是,通过t.project_id =分配ID时,并没有检查这个特定的ID是否真的存在。您必须使用自定义验证或使用Validates Existence GEM 进行检查。

要使用带有 Remarkable 匹配器的 RSpec 测试这些关联,请执行以下操作:

describe Task do
  it { should validate_presence_of :project_id }
  it { should validate_associated :project }
end

【讨论】:

  • 哇,这让我大开眼界。而且我在 Rails 3.2.3 中得到了意想不到的结果。试试这个。在任务模型中,添加 validates :project, :presence =&gt; true 但不添加 :associated 验证。在控制台中,创建一个 project_id=999 的新任务,并检查它是否为.valid?。 Rails 在 projects 表中查找记录,然后(可能是因为没有找到并且 project_id 仍然为 nil),验证失败。现在将验证更改为validates :project_id, :presence =&gt; true。 Rails 不读取项目并且验证成功。添加:associated,验证还是成功。
【解决方案2】:
validates :project, :associated => true  
validates :project_id, :presence => true

如果您想确保存在关联,您需要 测试用于映射关联的外键是否存在, 而不是关联的对象本身。 http://guides.rubyonrails.org/active_record_validations_callbacks.html

attr_accessible :project_id

【讨论】:

  • 这是从文档中直接引用的,但在我使用 Rails 3.2.3 进行的测试中,它的工作原理正好相反。如果您验证关联 (:project, :presence =&gt; true) 的存在,如果项目表中不存在 project_id,则验证失败。但是,如果您验证 id (:project_id, :presence =&gt; true) 上的存在,则无论您为 project_id 分配什么值,.valid? 都会返回 true。添加validates :project, :associated =&gt; true 无济于事——它仍然接受不存在的project_ids。对如何验证关联感到非常困惑!
【解决方案3】:

编辑:假设关联不是可选的......

我可以让它彻底验证的唯一方法是:

validates_associated :project
validates_presence_of :project_id, 
    :unless => Proc.new {|o| o.project.try(:new_record?)}
validates_presence_of :project, :if => Proc.new {|o| o.project_id}

第一行验证关联的项目是否有效,如果有的话。第二行坚持 project_id 存在,除非关联的项目存在并且是新的(如果它是新记录,它还没有 ID)。第三行确保如果存在 ID,即如果关联的项目已经保存,则项目存在。

如果您将保存的项目分配给project,ActiveRecord 将为任务分配一个project_id。如果您将未保存/新项目分配给project,它将使project_id 留空。因此,我们希望确保project_id 存在,但仅在处理已保存的项目时;这是在上面的第二行中完成的。

反之,如果你给project_id分配一个代表真实项目的数字,ActiveRecord会用对应的项目对象填充project。但是,如果您分配的 ID 是伪造的,它会将project 保留为 nil。因此上面的第三行,如果project_id 被填写,确保我们有一个project——如果你提供一个虚假的ID,这将失败。

查看测试这些验证的 RSpec 示例:https://gist.github.com/kianw/5085085

【讨论】:

    【解决方案4】:

    Joshua Muheim 的解决方案有效,但我讨厌不能简单地将项目链接到具有这样 ID 的任务:

    t = Task.new
    t.project_id = 123 # Won't verify if it's valid or not.
    

    所以我想出了这个:

    class Task < ActiveRecord:Base
    
        belongs_to :project
    
        validates :project_id, :presence => true
        validate :project_exists
    
        private
        def project_exists
            # Validation will pass if the project exists
            valid = Project.exists?(self.project_id)
            self.errors.add(:project, "doesn't exist.") unless valid
        end
    
    end
    

    【讨论】:

      猜你喜欢
      • 2012-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-28
      • 2020-08-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多