Imagine that you have a model object that is storing a string that is encoded to represent a large amount of data in a small amount of space. For instance, let's say you have a calendar item that is using a string that is 31 characters long to represent the state of each day in the month (this is purely hypothetical, of course). Let's say you want to be able to block of segments of the month at a time, labeling them as "AVAILABLE" or "UNAVAILABLE". You might have a method on your model object that looks something like this:
def mark_days(day_1,day_2,status)
marker = (status == "AVAILABLE") ? "A" : "U"
while(day_1 <= day_2)
self.availability[day_1 - 1] = marker[0]
day_1 += 1
end
self.save!
end
Cool. Now you run your unit tests, and you notice a strange thing: they're failing. Adding some debug output, you can see that the model object is indeed having it's field updated correctly, and that all the validation is passing, and that the save method is indicating that it saved properly, but when you reload the object from the database nothing has changed!
"Bummer" you might say to yourself. I know I did. After some digging into the ActiveRecord documentation, though, I have found the solution, and I hope that in the future someone who needs it comes across this blog post.
When dealing with a string field on an AR model object, the model will not save that field unless it thinks it has been changed. Usually this is done by direct assignment. For example, if you have a person object named steve, and you want to change his name to fred, you would simply say:
person.name = "Fred"
When the name attribute is assigned to, AR knows that the field has been changed, and when the next save call comes the name will be persisted. However, if you will note my example above, I'm not reassigning the field, I'm just modifying parts of it in place. This causes active record to basically say "Since the reference hasn't changed, the data hasn't changed, and I don't need to save anything".
Never fear, though, as there's an easy way around it. If you want to modify a field "in-place", you must notify ActiveRecord of the incoming change. Observe this corrected code snippet:
def mark_days(day_1,day_2,status)
marker = (status == "AVAILABLE") ? "A" : "U"
self.availability_will_change!
while(day_1 <= day_2)
self.availability[day_1 - 1] = marker[0]
day_1 += 1
end
self.save!
end
By using the "[attribute name]_will_change!" method, you tell ActiveRecord that despite all of the usual indicators, this field has changed and must be saved next time the call comes in. Problem solved, and the tests are passing. May someone benefit from this tidbit of knowledge.

1 comments:
Cool. Never seen that before.
Post a Comment