I've got an ActiveRecord (2.3.5) model that occasionally exhibits incorrect behavior that appears to be related to a problem in its dynamic initialization. Here's the code:
class Widget < ActiveRecord::Base
extend ActiveSupport::Memoizable
serialize :settings
VALID_SETTINGS = %w(show_on_sale show_upcoming show_current show_past)
VALID_SETTINGS.each do |setting|
class_eval %{
def #{setting}=(val); self.settings[:#{setting}] = (val == "1"); end
def #{setting}; self.settings[:#{setting}]; end
}
end
def initialize_settings
self.settings ||= { :show_on_sale => true,
:show_upcoming => true }
end
after_initialize :initialize_settings
# All the other stuff the model does
end
The idea was to use a single record field (settings) to persist a bunch of configuration data for this object, but allow all the settings to seamlessly work with form helpers and the like. (Why this approach makes sense here is a little out of scope, but let's assume that it does.) Net-net, Widget should end up with instance methods (eg #show_on_sale= #show_on_sale) for all the entires in the VALID_SETTINGS array. Any default values should be specified in initialize_settings.
And indeed this works, mostly. In dev and staging, no problems at all. But in production, the app sometimes ends up in a state where a) any writes to the dynamically generated setters fail and b) none of the default values appear to be set - although my leading theory is that the dynamically generated reader methods are just broken. The code, db, and environment is otherwise identical between the three.
A typical error message / backtrace on the fail looks like:
IndexError: index 141145 out of string
(eval):2:in []='
(eval):2:inshow_on_sale='
[GEM_ROOT]/gems/activerecord-2.3.5/lib/active_record/base.rb:2746:in send'
[GEM_ROOT]/gems/activerecord-2.3.5/lib/active_record/base.rb:2746:inattributes='
[GEM_ROOT]/gems/activerecord-2.3.5/lib/active_record/base.rb:2742:in each'
[GEM_ROOT]/gems/activerecord-2.3.5/lib/active_record/base.rb:2742:inattributes='
[GEM_ROOT]/gems/activerecord-2.3.5/lib/active_record/base.rb:2634:in `update_attributes!'
...(then controller and all the way down)
Ideas or theories as to what might be going on?
My leading theory is that something is going wrong in instance initialization wherein the class instance variable settings is ending up as a string rather than a hash. This explains both the above setter failure (:show_on_sale is being used to index into the string) and the fact that getters don't work (an out of bounds [] call on a string just returns nil). But then how and why might settings occasionally end up as a string rather than hash?