Consider the following:
ScheduledSession ------> Applicant <------ ApplicantSignup
Note:
- ScheduledSession will permanently exist in the system; think of it as a class or course.
- The goal is to check the ApplicantSignup model for an attribute in a ScheduledSession during
signups_controller#create
Associations
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 resources i.e. the #create action will have a path similar to /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
You will notice that I set the virtual attribute above @session.duration to prevent the session from being considered invalid. The real βmagicβ if you find yourself in @signup = ApplicantSignup.new(params[:signup].merge(:sessions => [@session])) , which now means that in the model I can choose from self.scheduled_sessions and get access to the ScheduledSession with which this ApplicantSignup is built, although at this point in time there is no record present in the connection table.
Model confirmations, for example, look like
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
This works well in development , and the checks work as expected - but how do I test with Factory Girl? I want unit tests to guarantee the logical logic that I implemented after all - Of course, this is after the fact, but still remains one way to switch to TDD.
You will notice that I got a comment raise StandardError, self.scheduled_sessions.inspect in the last check above - this returns [] for self.scheduled_sessions , which indicates that my Factory installation is not true.
One of many attempts =)
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
The first one passes, but I honestly do not believe that it correctly checks the model of the applicant_signup; the fact that self.scheduled_sessions returns [] just means that the above is incorrect.
Is it possible that I'm trying to test something outside the scope of Factory Girl, or is there a much better approach to solving this? Appreciate all comments, advice and constructive criticism!
Update:
- Not sure what this is called, but this is an approach, at least with regard to how it is implemented at the controller level
- I need to consider ignoring Factory Girl for the association aspect and try to return
scheduled_session by mocking scheduled_sessions on the applicant_signup model.
Plants
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|