Рельсы ActiveRecord скажи не спрашивай

Имея два класса, как это:

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? и как ограничение в маршрутах, а также в других классах, где мне нужно определить, существует ли он а), и б) активен.

Взято из другого вопроса по SO:

Скажи-не-спроси, в основном, означает, что вы не должны запрашивать объект о его состоянии, принимать решение на основе его состояния и затем сообщать тому же объекту, что делать. Если у объекта есть вся информация, в которой он нуждается, он должен решить для себя.

Хотя я этого не делаю, мой код чувствует себя довольно взаимосвязанным, как в отношении связи между сайтом и подпиской, так и в связи с ActiveRecord, что затрудняет тестирование, не затрагивая базу данных.

Как бы вы структурировали его, чтобы не запрашивать соответствующую подписку, чтобы определить состояние сайта? А также, вы считаете это нарушением правил "не спрашивайте"?

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
Другие вопросы по тегам