Рельсы 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