0

次のような 2 つのクラスがあります。

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に関する別の質問から取得:

聞かないで伝えるとは、基本的に、オブジェクトの状態についてクエリを実行し、その状態に基づいて決定を下し、同じオブジェクトに何をすべきかを伝えてはならないことを意味します。オブジェクトが必要な情報をすべて持っている場合は、それ自体で決定する必要があります。

私はそうしませんが、私のコードは、サイトとサブスクリプションの間の結合だけでなく、データベースに触れずにテストするのが難しくなる ActiveRecord への結合に関しても、かなり結合されているように感じます。

サイトの状態を判断するために、関連するサブスクリプションに問い合わせるのを避けるには、どのように構成しますか? また、これは「聞くな」の違反だと思いますか?

4

1 に答える 1

1

ActiveRecord を使用すると、いくつかのカップリングが発生しますが、問題はありません。実行していることは LOD に違反していません。フィールドを非正規化activeしてサイト自体に保持することはできますが、私はそうしません。

私が変更することの 1 つは、サブスクリプションを熱心に読み込むことです。

#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

少なくとも、これにより、ホスト名がアクティブかどうかを判断するために 2 つのクエリを実行する必要がなくなります。

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 に答える