I'm running into a very odd issue with a validator that is meant to be used only when this module gets included. When running certain tests, I would get a validation failure as expected. However, the error is not a RecordInvalid
; instead, it's an ArgumentError: wrong number of arguments (0 for 1)
, which makes absolutely no sense in this case.
Here is the mixin containing the validation code.
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
As you may have noticed, I added some print statements to help me debug the state of certain variables during testing. I also changed the method parameters to take a variable number of parameters so I could see what's actually being sent to the methods. (Note 1: as a result of making these changes to make things easier to debug, the error has been changed to a MethodError: You have a nil object when you didn't expect it!
. Note 2: the method signature changes mainly consist of def method(history) -> def method(*args)
and setting history = args[0]
) Here is some output as a result of these statements:
"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"
[]
Given the contents of the account_state_integrity
method, I can't see any reason why end_with_nil
is suddenly missing an argument that is clearly being passed to it in account_state_integrity
.
I'll make this clear in case this bug is caused by an already-fixed legacy bug: we're using old versions of Ruby (1.8.7) and Rails (2.3.14). We're going to be migrating to much more recent versions of Ruby and Rails in the very near future.
Edit: Full stack trace output from the unit 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:
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)
The problem is ActiveRecord::Errors.add is meant to be used when adding errors to attributes.
end_with_nil
is not an attribute. You need to use ActiveRecord::Errors.add_to_base instead.
You should correct the other errors.add
lines too.
What happens is Shoulda (your test runner) notices that there is an error on an attribute named end_with_nil
, and it will try and generate a message for a failed test that goes something like this:
"You have an error for end_with_nil: value was #{record.end_with_nil},
when it should have been xyz".
..and hence end_with_nil
gets called without arguments and everything fails.