Having a large model stack and using doll caching techniques extensively, one ends up with lots of parent models been "touched" after a model update.
While testing, this seems to be a time waster unless you try to test that feature specifically.
Is there a way to prevent models to touch
their belongs_to
associations for the test environment or at a test level?
UPDATE 1:
My first attempt to the case would be to
# /config/initializers/extensions.rb
#
class ActiveRecord::Base
def self.without_touch_for_association(association_name, &block)
association_name = association_name.to_sym
association = self.reflect_on_all_associations(:belongs_to).select { |reflection| reflection.name == association_name }.first
options = association.options
association.instance_variable_set :@options, options.except(:touch)
yield
association.instance_variable_set :@options, options
end
end
Post.without_touch_for_association(:user) do
Post.last.save
end
Of course, no success and saving Post.last
still touches it's User
.
UPDATING RATIONALE:
I understand and agree that this approach may be a source of bugs and it's not a good practice at all. The thing is that I have a huge suite with lots of both integration and unit tests. Doll caching also gets deep in the model tree. Every time I look at the logs, I see a significant % of touch-related queries. I know the best way would be optimizing the unit tests to add more mocking and stubbing and less persistence. Solving the issue within integration tests is more difficult.
In any case, I'm asking this question for the sake of learning and research. I am interested in exploring the potential speed improvements of this technique.
SOLUTION: see my own answer below for the working code.
For Rails >= 4.2
Thanks to @Dorian, in Rails 4.2 the way to go is using ActiveRecord::NoTouching.
For Rails < 4.2
My working code in rspec support file:
# /spec/support/active_record_extensions.rb
class ActiveRecord::Base
def self.without_touch_for_association(association, &block)
method_name = :"belongs_to_touch_after_save_or_destroy_for_#{association}"
return unless self.instance_methods.include?(method_name)
method = self.send(:instance_method, method_name)
self.send(:define_method, method_name) { true }
yield
self.send(:define_method, method_name, method)
nil
end
def self.disable_touch_associations!
associations = self.reflect_on_all_associations(:belongs_to)
associations.each do |association|
self.without_touch_for_association association.name do
return
end
end
nil
end
end
Add this to your ./spec/spec_helper.rb
to disable all touch calls for any model defined, for the whole test suite:
RSpec.configure do |config|
if ENV['SILENCE_TOUCHES']
config.before :suite do
ActiveRecord::Base.descendants.each {|model| model.disable_touch_associations! }
end
end
end
Temporarely disabling a touch for a model and association in a particular test.
Post.without_touch_for_association(:user) do
Post.last.save
end
Thanks to @xlembouras below for pointing me to the right direction!
I'm playing with this feature on our tests and I'm noticing a 25% reduction in test suite speed, for a 30min test suite. I may post more accurate results after more thorough research.