2

拥有大型模型堆栈并广泛使用玩偶缓存技术,最终会在模型更新后“触及”许多父模型。

在测试时,这似乎是在浪费时间,除非您尝试专门测试该功能。

有没有办法阻止模型与测试环境或测试级别touch的关联?belongs_to

更新 1:

我对这个案子的第一次尝试是

# /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

当然,没有成功和省钱Post.last还是碰它的User

更新理由:

我理解并同意这种方法可能是错误的来源,这根本不是一个好的做法。问题是我有一个包含大量集成和单元测试的巨大套件。娃娃缓存也深入模型树。每次查看日志时,我都会看到很大一部分与触摸相关的查询。我知道最好的方法是优化单元测试以添加更多的模拟和存根以及更少的持久性。在集成测试中解决问题更加困难。

无论如何,我问这个问题是为了学习和研究。我有兴趣探索这种技术的潜在速度改进。

解决方案:有关工作代码,请参阅下面我自己的答案。

4

4 回答 4

11

假设您使用的是 Rails 4.1.4 或更高版本:

User.no_touching do
  Post.last.save
end

甚至

ActiveRecord::Base.no_touching do
  Post.last.save
end

请参阅ActiveRecord::NoTouching

于 2014-07-15T08:20:27.947 回答
3

我不同意为了测试目的而改变代码的想法。从我的角度来看,测试应该是一个独立的程序。

正如我所看到的,您应该为您的测试套件提供一种方法,以便仅在某些情况下更改模型的行为。

以下代码

class Book < ActiveRecord::Base
  belongs_to :author, touch: true
end

class Author < ActiveRecord::Base
  has_many :books
end

这是你的情况将定义一个实例方法

belongs_to_touch_after_save_or_destroy_for_author

Book为班级幕后。(感谢 AR http://apidock.com/rails/ActiveRecord/Associations/Builder/BelongsTo/add_touch_callbacks

因此,在您的测试代码中,您可以覆盖该方法以执行不同的操作或根本不执行任何操作!

在我的情况下,我将 Rspec 与FactoryGirl一起使用,所以我所做的是为重新定义该对象的Book类创建一个特殊的工厂特征belongs_to_touch_after_save_or_destroy_for_author

FactoryGirl.define do
  factory :book do
    ...
    ...
  end

  trait :no_touch do
    before(:create) do |book_no_touch|
      def book_no_touch.belongs_to_touch_after_save_or_destroy_for_author
        true
      end
    end
  end
end

这样,当您需要测试与相关对象无关的东西时,您可以使用该工厂创建一个书籍对象

book = FactoryGirl.create(:book, :no_touch)

于 2013-10-03T10:37:30.213 回答
3

对于 Rails >= 4.2

感谢@Dorian,在 Rails 4.2 中要走的路是使用ActiveRecord::NoTouching

对于 Rails < 4.2

我在 rspec 支持文件中的工作代码:

# /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

将此添加到您的./spec/spec_helper.rb以禁用整个测试套件定义的任何模型的所有触摸调用:

RSpec.configure do |config|
  if ENV['SILENCE_TOUCHES']
    config.before :suite do
      ActiveRecord::Base.descendants.each {|model| model.disable_touch_associations! }
    end
  end
end

在特定测试中暂时禁用模型和关联的触摸。

Post.without_touch_for_association(:user) do
  Post.last.save
end

感谢下面的@xlembouras 为我指明了正确的方向!

我在我们的测试中使用了这个功能,我注意到测试套件的速度降低了 25%,对于 30 分钟的测试套件。经过更深入的研究后,我可能会发布更准确的结果。

于 2013-10-06T00:03:34.840 回答
0

我不确定这是否可行,但您可以尝试以下方法:

belongs_to :foo, touch: APP_CONFIG['doll_touch']

其中 APP_CONFIG 是按照本指南设置的应用程序参数。

因此,在配置的生产/开发部分中,您设置doll_touch为.truefalse

于 2013-10-01T12:53:38.927 回答