【问题标题】:Argument provided to Rails validate function is sometimes missing提供给 Rails 验证功能的参数有时会丢失
【发布时间】:2012-06-20 19:52:28
【问题描述】:

我遇到了一个非常奇怪的问题,即只有在包含此模块时才使用的验证器。在运行某些测试时,我会按预期得到验证失败。但是,错误不是RecordInvalid;相反,它是一个ArgumentError: wrong number of arguments (0 for 1),在这种情况下完全没有意义。

这是包含验证码的 mixin。

module AccountStateIntegrityMixin
  def self.included(base)
    base.class_eval do
      base.validate :account_state_integrity
    end
  end

  def account_state_integrity
    history = self.account_state_history_entries.order("start ASC").all
    return if history.blank?

    vald_methods = [
      :end_with_nil,
      :no_self_transitions,
      :no_overlaps_or_gaps
    ]

    vald_methods.each do |m|
      p "history = #{history.inspect}"
      if !self.send(m, history)
        return
      end
    end
  end

  def no_self_transitions(*args)
    p "no_self_transitions"
    p args
    history = args[0]
    # I want everything except the last element. I want to compare each
    # element with its successor
    history[0..-2].each_with_index do |entry, i|
      next_elem = history[i+1]
      if entry.account_state_id == next_elem.account_state_id
        self.errors.add(:account_state_history, "has a self-transition entries with " +
                        "IDs #{entry.id} and #{next_elem.id}")
        self.errors.add(:no_self_transitions, "violated")
        return false
      end
    end

    true
  end

  def end_with_nil(*args)  # ArgumentError was being thrown here
    p "end_with_nil"
    p args
    history = args[0]
    last = history.last  # with debugging statement, NoMethodError here

    if last.end != nil
      self.errors.add(:account_state_history, "is missing a nil ending. Offending " +
                      "entry has ID = #{last.id}")
      self.errors.add(:end_with_nil, "violated")
      return false
    end

    true
  end

  def no_overlaps_or_gaps(*args)
    p "no_overlaps_or_gaps"
    p args
    history = args[0]
    # Again, everything except the last element
    history[0..-2].each_with_index do |entry, i|
      next_elem = history[i+1]
      if next_elem.start != entry.end
        self.errors.add(:account_state_history, "has an overlap or gap between " +
                        "entries with IDs #{entry.id} and #{next_elem.id}")
        self.errors.add(:no_overlaps_or_gaps, "violated")
        return false
      end
    end

    true
  end
end

您可能已经注意到,我添加了一些打印语句来帮助我在测试期间调试某些变量的状态。我还更改了方法参数以采用可变数量的参数,因此我可以看到实际发送到方法的内容。 (注1:由于进行了这些更改以使事情更容易调试,错误已更改为MethodError: You have a nil object when you didn't expect it!。注2:方法签名更改主要包括def method(history) -> def method(*args)和设置history = args[0])这里是这些语句的一些输出:

"history = [#<AccountStateHistoryEntry id: 1007, account_id: 684, ... >]"
"end_with_nil"
[[#<AccountStateHistoryEntry id: 1007, account_id: 684, ... >]]
"history = [#<AccountStateHistoryEntry id: 1007, account_id: 684, ... >]"
"no_self_transitions"
[[#<AccountStateHistoryEntry id: 1007, account_id: 684, ... ]]
"history = [#<AccountStateHistoryEntry id: 1007, account_id: 684, ... >]"
"no_overlaps_or_gaps"
[[#<AccountStateHistoryEntry id: 1007, account_id: 684, ... >]]
"history = [#<AccountStateHistoryEntry id: 1007, account_id: 684, ... >, <AccountStateHistoryEntry id: 1008, account_id: 684, ... >]"
"end_with_nil"
[[#<AccountStateHistoryEntry id: 1007, account_id: 684, ... >, #<AccountStateHistoryEntry id: 1008, account_id: 684, ... >]]
"end_with_nil"
[]

鉴于account_state_integrity 方法的内容,我看不出end_with_nil 突然缺少一个明显在account_state_integrity 中传递给它的参数的任何原因。

如果这个错误是由一个已经修复的遗留错误引起的,我会澄清这一点:我们使用的是旧版本的 Ruby (1.8.7) 和 Rails (2.3.14)。我们将在不久的将来迁移到更新版本的 Ruby 和 Rails。

编辑:单元测试的完整堆栈跟踪输出

A free-trial account An account with no state with an account state history entry set to end in the future should still be able to end the account state:
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.last
app/models/account_state_integrity_mixin.rb:51:in `end_with_nil'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:42:in `send'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:42:in `value'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:125:in `default_options'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:36:in `full_message'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:287:in `full_messages'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:287:in `map'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:287:in `full_messages'
/Users/Daniel/.rvm/gems/.../activesupport/lib/active_support/whiny_nil.rb:52:in `inject'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:286:in `each'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:286:in `inject'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:286:in `full_messages'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:13:in `initialize'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:1101:in `new'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/validations.rb:1101:in `save_without_dirty!'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/dirty.rb:87:in `save_without_transactions!'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/transactions.rb:200:in `save!'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `transaction'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/transactions.rb:182:in `transaction_without_always_new'
config/initializers/always_nest_transactions.rb:11:in `transaction'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/transactions.rb:200:in `save!'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/transactions.rb:208:in `rollback_active_record_state!'
/Users/Daniel/.rvm/gems/.../activerecord/lib/active_record/transactions.rb:200:in `save!'
app/models/account_state_lib.rb:260:in `clear_capability_overrides!'
app/models/account_state_lib.rb:321:in `clear_capabilities_cache'
app/models/account_state_lib.rb:256:in `clear_capability_overrides!'
app/models/account_state_lib.rb:121:in `end_state!'
test/unit/account_state_lib_test.rb:173:in `__bind_1340223707_845108'
/Users/Daniel/.rvm/gems/.../thoughtbot-shoulda-2.11.1/lib/shoulda/context.rb:382:in `call'
/Users/Daniel/.rvm/gems/.../thoughtbot-shoulda-2.11.1/lib/shoulda/context.rb:382:in `test: A free-trial account An account with no state with an account state history entry set to end
in the future should still be able to end the account state. '
/Users/Daniel/.rvm/gems/.../activesupport/lib/active_support/testing/setup_and_teardown.rb:62:in `__send__'
/Users/Daniel/.rvm/gems/.../activesupport/lib/active_support/testing/setup_and_teardown.rb:62:in `run'
 (2.221 s) (0.000 s)

【问题讨论】:

  • 您发布了一堵代码,但忘记包含错误所在的行号。
  • 另外,为什么end_with_nil 被调用 3 次以获得记录 id 1007?您正在从其他地方调用它,而不仅仅是您的 each 循环。查看错误消息的调用堆栈。打印输出中的最后一个条目是end_with_nil,但没有history = ...。它是从代码中的其他地方调用的。
  • 单元测试在第 50 行显示失败。第 50 行对应于 end_with_nil 的方法签名。我将编辑我的问题以使其更清楚。
  • 你在测试什么? RSpec,迷你测试?您需要打开完整的回溯以查看哪里出错了。见这里:stackoverflow.com/questions/7649037/… 或这里:stackoverflow.com/questions/9219461/…
  • 在没有深入了解测试的实现细节的情况下,end_with_nil 被调用 3 次的原因很可能是因为 account_state_integrity 被调用了 3 次。诚然,这种特殊技术可能不是进行此验证的最佳方式,并且每次保存记录时验证都会遍历历史记录。

标签: ruby ruby-on-rails-2


【解决方案1】:

问题是ActiveRecord::Errors.add 是在向属性添加错误时使用的。

end_with_nil 不是属性。您需要改用ActiveRecord::Errors.add_to_base

您也应该更正其他 errors.add 行。

发生的情况是,Shouda(您的测试运行者)注意到名为 end_with_nil 的属性存在错误,它会尝试为失败的测试生成一条消息,如下所示:

"You have an error for end_with_nil: value was #{record.end_with_nil}, 
 when it should have been xyz".

..因此 end_with_nil 被调用时不带参数,一切都失败了。

【讨论】:

  • +1 支持,因为您不仅费尽心思单独帮助我(通过聊天讨论),而且还对问题的根源进行了大量研究以提出一个做得好,但简洁,回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多