'Is there a clean API for resetting instance variables on 'reload' in ActiveRecord?
In an ActiveRecord::Base model, I can reset the state of the model to what it was when I got it from the database with reload, as long as the attribute I'm setting maps to a table column:
user = User.first
user.email #=> "[email protected]"
user.email = "[email protected]"
user.email #=> "[email protected]"
user.reload
user.email #=> "[email protected]"
But if I add a custom attribute, the only way I've found to have it act the same is like this:
class User < ActiveRecord::Base
attr_accessor :user_agent
def reload
super
self.user_agent = nil
self
end
end
My question is, is there some API to make non-database-column-attributes reset on reload? Something like:
class User < ActiveRecord::Base
# this
reloadable_attr_accessor :user_agent
# or this
def user_agent
@user_agent
end
def user_agent=(value)
set_instance_var_that_resets_on_reload("@user_agent", value)
end
end
Does that exist in Rails somewhere?
Solution 1:[1]
Rework Jean-Do's answer slightly. It doesn't break default instance_variables and relations.
after_initialize do
@default_instance_variables = instance_variables
end
def reload(options = nil)
super
self.instance_variables.each do |ivar|
if ivar == :'@default_instance_variables' ||
@default_instance_variables.include?(ivar)
next
end
remove_instance_variable(ivar)
end
self
end
Solution 2:[2]
I took gayavat's answer and reworked it into my test_helper.rb file, because I didn't want to override the usual #reload method.
class ActiveRecord::Base
attr_accessor :default_instance_variables
after_initialize do
@default_instance_variables = instance_variables
end
end
def reload_ivars(obj)
obj.reload
obj.instance_variables.each do |ivar|
if ivar == :'@default_instance_variables' ||
obj.default_instance_variables.include?(ivar)
next
end
obj.send(:remove_instance_variable, ivar)
end
end
When I need to reload something in a test I just call reload_ivars(object).
Solution 3:[3]
I've reworked gavayat's answer slightly. I've made three changes:
- Using a class variable to store
default_instance_variablesmakes a bit more sense here (unless you're conditionally instantiating instance variables in your constructor, but you don't want to do that). This way you don't need to check fordefault_instance_variablesitself insidereload. - I've moved the
supercall to the end of the function. This ensures that this method returns whateverActiveRecord::Base.reloadreturns, and gets rid of a line of code. - The options parameter is actually passed into the
supercall.
after_initialize do
@@default_instance_variables ||= instance_variables
end
def reload(options = nil)
self.instance_variables.each do |ivar|
if @@default_instance_variables.include?(ivar)
next
end
remove_instance_variable(ivar)
end
super(options)
end
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | gayavat |
| Solution 2 | Alex Ghiculescu |
| Solution 3 |
