For over a month, I received occasional reports that an existing feature, adding secondary accounts, wasn't working. I could never reproduce the failure in any browser or with a unit test, though.
Recently, I received a report about another feature, creating an account, that failed. I could see it fail if I watched the application log while the feature was used, but, again, I couldn't reproduce the failure.
Calling for Reinforcements
I revisited the problem this week. After many more attempts at reproducing the problem, I was out of ideas. I called for reinforcements. I needed another pair of eyes on this one.
It wasn't long before there was a spark! My pair remembered facing a problem on an old project. It involved mass-assignment. The feature on my project used mass-assignment. It involved overwritten mutators. The feature on my project used overwritten mutators. Could this problem be the same?
The Overwritten Mutators
Here are the overwritten mutators,
They either store their value locally or on an object backed by LDAP.
This decision is made in
store_locally?, which checks another attribute named
Here's the code that has been intermittently failing.
It would cause the password confirmation validation to fail, complaining that the provided password and password confirmation didn't match.
store_locally?, depend on
create_login_account builds the hash of attributes in an
order which puts
role_type first, there is no
on the order in which the attributes will be used to assign the attributes on
password_confirmation could be assigned
To ensure that
role_type is assigned before
password_confirmation, we abandon mass-assignment, instead assigning the
attributes one at a time.
This approach, though not necessarily idiomatic, solves the problem. I added a brief comment in the project so that anyone, including me, who works on this code later will know why it deviates from the norm.
- There's no guarantee in Ruby 1.8, anyway, which this application uses. In Ruby 1.8, “The order in which you traverse a hash by either key or value may seem arbitrary, and will generally not be in the insertion order.” In Ruby 1.9 and 2.0, “Hashes enumerate their values in the order that the corresponding keys were inserted.”