0

有两个这样的类:

class Site < ActiveRecord::Base
  has_one :subscription, dependent: :destroy

  def self.hostname_active?(hostname)
    site = where(hostname: hostname)
    site.exists? && site.first.active?
  end

  def active?
    subscription.active?
  end
end

class Subscription < ActiveRecord::Base
  belongs_to :site

  def active?
    (starts_at..ends_at).cover?(Date.current)
  end
end

describe Site do
  let(:site) { Fabricate.build(:site) }

  describe "#hostname_active?" do
    it "Returns true if site with hostname exists & is active" do
      described_class.stub_chain(:where, :exists?).and_return(true)
      described_class.stub_chain(:where, :first) { site }
      site.stub(:active?).and_return(true)
      described_class.hostname_active?('www.example.com').should be_true
    end

    it "Returns false if site with hostname doesn't exist" do
      described_class.stub_chain(:where, :exists?).and_return(false)
      described_class.stub_chain(:where, :first) { site }
      site.stub(:active?).and_return(false)
      described_class.hostname_active?('www.example.com').should be_false
    end

    it "Returns false if site is not active" do
      described_class.stub_chain(:where, :exists?).and_return(true)
      described_class.stub_chain(:where, :first) { site }
      site.stub(:active?).and_return(false)
      described_class.hostname_active?('www.example.com').should be_false
    end
  end
end

在相关订阅确定站点是否处于活动状态的情况下,我使用该方法hostname_active?作为路由中的约束以及在需要确定它是否 a) 存在和 b) 处于活动状态的其他类中。

取自关于 SO 的另一个问题:

Tell-don't-ask 基本上意味着你不应该查询一个对象的状态,根据它的状态做出决定,然后告诉同一个对象要做什么。如果对象拥有它需要的所有信息,它应该自己决定。

虽然我不这样做,但我的代码确实感觉非常耦合,无论是在站点和订阅之间的耦合方面,还是在与 ActiveRecord 的耦合方面,这使得在不接触数据库的情况下很难进行测试。

您将如何构建它以避免询问相关订阅以确定站点的状态?而且,你会认为这违反了“不问不问”的规定吗?

4

1 回答 1

1

使用 ActiveRecord,你会有一些耦合,这没关系,你正在做的事情不会违反 LOD。您可以对该字段进行非规范化active以将其保留在站点本身上,但我不会这样做。

我要改变的一件事是急切地加载订阅。

#Just showing changes

class Site < ActiveRecord::Base
  scope :active, includes(:subscription).merge(Subscription.active)
  has_one :subscription, dependent: :destroy

  def self.hostname_active?(hostname)
    active.where(hostname: hostname).exists?
  end
end

class Subscription < ActiveRecord::Base
  scope :active, where(arel_table[:starts_at].lteq(Date.current), arel_table[:ends_at].gteq(Date.current))
end

至少,这将使您不必执行两次查询来确定主机名是否处于活动状态。

至于存根 ActiveRecord,我通常不会看到这种情况发生。普遍接受的做法是使用夹具或工厂来构建您的测试对象。就个人而言,我使用 FactoryGirl:https ://github.com/thoughtbot/factory_girl_rails 。

在你的情况下,我会有这样的工厂:

FactoryGirl.define do
  factory :subscription do
    site
    factory :active_subscription do
      starts_at { Date.today.beginning_of_month }
      ends_at { Date.today.end_of_month }
    end

    factory :inactive_subscription do
      starts_at { Date.today.beginning_of_month - 3.months }
      ends_at { Date.today.end_of_month - 3.months }
    end
  end
end

FactoryGirl.define do
  factory :site do
    sequence(:hostname, 1000) {|h| "site#{h}.example.com" }
    factory :active_site do
      after(:create) do |site|
        FactoryGirl.create(:active_subscription, site: site)
      end
    end
    factory :inactive_site do
      after(:create) do |site|
        FactoryGirl.create(:inactive_subscription, site: site)
      end
    end
  end
end

这将使我的规格看起来像:

describe Site do 
  describe "Active site" do
    subject(:site) { FactoryGirl.create :active_site }
    its(:active?) { should eq(true) }
  end

  #etc...
end
于 2012-12-04T19:15:05.017 回答