Рассмотрим следующее:
ScheduledSession ------> Applicant <------ ApplicantSignup
Примечания:
- Запланированная сессия будет существовать в системе всегда; думайте об этом как о классе или курсе.
- Целью здесь является проверка модели ApplicantSignup по атрибуту ScheduledSession во время
signups_controller#create
ассоциации
class ScheduledSession < ActiveRecord::Base
has_many :applicants, :dependent => :destroy
has_many :applicant_signups, :through => :applicants
#...
end
class ApplicantSignup < ActiveRecord::Base
has_many :applicants, :dependent => :destroy
has_many :scheduled_sessions, :through => :applicants
#...
end
class Applicant < ActiveRecord::Base
belongs_to :scheduled_session
belongs_to :applicant_signup
# TODO: enforce validations for presence
# and uniqueness constraints etc.
#...
end
SignupsController
Ресурсы RESTful, т. Е. Действие #create
будет иметь путь, аналогичный /scheduled_sessions/:id/signups/new
def new
@session = ScheduledSession.find(params[:scheduled_session_id])
@signup = @session.signups.new
end
def create
@session = ScheduledSession.find(params[:scheduled_session_id])
@session.duration = (@session.end.to_time - @session.start.to_time).to_i
@signup = ApplicantSignup.new(params[:signup].merge(:sessions => [@session]))
if @signup.save
# ...
else
render :new
end
end
Вы заметите, что я устанавливаю виртуальный атрибут выше @session.duration
, чтобы сессия не считалась недействительной. Настоящая «магия», если вы будете происходить в @signup = ApplicantSignup.new(params[:signup].merge(:sessions => [@session]))
, что теперь означает, что в модели я могу выбрать из self.scheduled_sessions
и получить доступ к ScheduledSession, против которого создается этот объект ApplicantSignup, даже если в этот самый момент времени, в объединяющей таблице нет записей .
Проверка модели, например, выглядит как
def ensure_session_is_upcoming
errors[:base] << "Cannot signup for an expired session" unless self.scheduled_sessions.select { |r| r.upcoming? }.size > 0
end
def ensure_published_session
errors[:base] << "Cannot signup for an unpublished session" if self.scheduled_sessions.any? { |r| r.published == false }
end
def validate_allowed_age
# raise StandardError, self.scheduled_sessions.inspect
if self.scheduled_sessions.select { |r| r.allowed_age == "adults" }.size > 0
errors.add(:dob_year) unless (dob_year.to_i >= Time.now.strftime('%Y').to_i-85 && dob_year.to_i <= Time.now.strftime('%Y').to_i-18)
# elsif ... == "children"
end
end
Вышеописанное работает довольно хорошо в development
, и проверки работают как ожидалось & mdash; но как можно проверить с Factory Girl? Я хочу, чтобы модульные тесты гарантировали бизнес-логику, которую я реализовал после всего & mdash; конечно, это после факта, но все еще является одним из способов достижения TDD.
Вы заметите, что я получил комментарий raise StandardError, self.scheduled_sessions.inspect
в последней проверке выше & mdash; это возвращает []
для self.scheduled_sessions
, что указывает на то, что мои заводские настройки просто неверны.
Одна из множества попыток =)
it "should be able to signup to a session" do
scheduled_session = Factory.build(:scheduled_session)
applicant_signup = Factory.build(:applicant_signup)
applicant = Factory.create(:applicant, :scheduled_session => scheduled_session, :applicant_signup => applicant_signup)
applicant_signup.should be_valid
end
it "should be able to signup to a session for adults if between 18 and 85 years" do
scheduled_session = Factory.build(:scheduled_session)
applicant_signup = Factory.build(:applicant_signup)
applicant_signup.dob_year = 1983 # 28-years old
applicant = Factory.create(:applicant, :scheduled_session => scheduled_session, :applicant_signup => applicant_signup)
applicant_signup.should have(0).error_on(:dob_year)
end
Первый пройден, но я, честно говоря, не верю, что он правильно проверяет модель Applicant_signup; тот факт, что self.scheduled_sessions
возвращает []
, просто означает, что вышеприведенное просто неверно.
Вполне возможно, что я пытаюсь протестировать что-то, выходящее за рамки Factory Girl, или есть гораздо лучший подход к решению этой проблемы? Ценю все комментарии, советы и конструктивную критику!
Обновление:
- Не уверен, как это называется, но этот подход используется по крайней мере в отношении , как это реализовано на уровне контроллера
- Мне нужно подумать о игнорировании Factory Girl как минимум для аспекта ассоциации и попытаться вернуть
scheduled_session
, высмеивая scheduled_sessions
на модели applicant_signup
.
Фабрика
FactoryGirl.define do
factory :signup do
title "Mr."
first_name "Franklin"
middle_name "Delano"
last_name "Roosevelt"
sequence(:civil_id) {"#{'%012d' % Random.new.rand((10 ** 11)...(10 ** 12))}"}
sequence(:email) {|n| "person#{n}@#{(1..100).to_a.sample}example.com" }
gender "male"
dob_year "1980"
sequence(:phone_number) { |n| "#{'%08d' % Random.new.rand((10 ** 7)...(10 ** 8))}" }
address_line1 "some road"
address_line2 "near a pile of sand"
occupation "code ninja"
work_place "Dharma Initiative"
end
factory :session do
title "Example title"
start DateTime.civil_from_format(:local,2011,12,27,16,0,0)
duration 90
language "Arabic"
slides_language "Arabic & English"
venue "Main Room"
audience "Diabetic Adults"
allowed_age "adults"
allowed_gender "both"
capacity 15
published true
after_build do |session|
# signups will be assigned manually on a per test basis
# session.signups << FactoryGirl.build(:signup, :session => session)
end
end
factory :applicant do
association :session
association :signup
end
#...
end