diff --git a/.gitignore b/.gitignore
index b6f67370..83a3b08d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,6 @@ capybara-*.html
/public/system/*
/spec/tmp/*
/tmp/*
-/vendor/bundle
\ No newline at end of file
+/vendor/bundle
+# Ignore application configuration
+/config/application.yml
diff --git a/Gemfile b/Gemfile
index 077af147..d8499dec 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,12 +8,6 @@ source 'http://gems.github.com'
gem 'rails', '~>3.2.12'
-gem 'aasm'
-#gem 'exception_notification', :require => 'exception_notifier'
-gem 'figaro'
-gem 'formtastic'
-gem 'haml'
-
gem 'carmen'
gem 'ckeditor'
gem 'client_side_validations'
@@ -21,6 +15,8 @@ gem 'cocaine', :git => 'git://github.com/thoughtbot/cocaine.git'
gem 'capistrano'
gem 'devise'
gem 'eventbrite-client'
+gem 'figaro' # also look at #https://github.com/Squeegy/rails-settings
+gem 'haml'
gem 'jquery-rails', '~> 2.1.0'
gem 'jquery-ui-rails'
gem 'kaminari'
@@ -28,10 +24,11 @@ gem 'mysql2'
gem 'paperclip'
gem 'paper_trail'
gem 'rails_admin'
+gem 'redcarpet'
gem 'rvm-capistrano'
+gem 'state_machine'
gem 'whenever', :require => false
-
group :assets do
gem 'bootstrap-sass', '~> 2.1.1.0'
gem 'bootstrap-datepicker-rails', :require => 'bootstrap-datepicker-rails', :git => 'git://github.com/Nerian/bootstrap-datepicker-rails.git'
@@ -45,14 +42,19 @@ group :assets do
end
group :development do
+ gem 'awesome_print'
gem "better_errors"
gem 'binding_of_caller'
- gem 'map_by_method'
+ gem 'bond'
+ gem 'crack'
+ gem 'hirb-unicode'
gem 'meta_request'
+ gem 'net-http-spy'
gem 'rb-fchange', :require => false
gem 'rb-fsevent', :require => false
gem 'rb-inotify', :require => false
gem 'ruby_gntp'
+ gem 'ruby-graphviz', :require => 'graphviz'
gem 'simplecov'
gem 'what_methods'
gem 'wirble'
diff --git a/Gemfile.lock b/Gemfile.lock
index 96c2a850..0478e485 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -16,7 +16,6 @@ GEM
remote: http://rubygems.org/
remote: http://gems.github.com/
specs:
- aasm (3.0.16)
actionmailer (3.2.13)
actionpack (= 3.2.13)
mail (~> 2.5.3)
@@ -46,12 +45,14 @@ GEM
multi_json (~> 1.0)
addressable (2.3.4)
arel (3.0.2)
+ awesome_print (1.1.0)
bcrypt-ruby (3.0.1)
better_errors (0.8.0)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
binding_of_caller (0.7.1)
debug_inspector (>= 0.0.1)
+ bond (0.4.3)
bootstrap-sass (2.1.1.0)
bourne (1.4.0)
mocha (~> 0.13.2)
@@ -88,6 +89,7 @@ GEM
execjs
coffee-script-source (1.6.2)
columnize (0.3.6)
+ crack (0.3.2)
database_cleaner (0.9.1)
debug_inspector (0.0.2)
debugger (1.5.0)
@@ -129,8 +131,6 @@ GEM
railties (>= 3.1.1)
sass-rails (>= 3.1.1)
formatador (0.2.4)
- formtastic (2.2.1)
- actionpack (>= 3.0)
guard (1.7.0)
formatador (>= 0.2.4)
listen (>= 0.6.0)
@@ -147,6 +147,10 @@ GEM
haml (3.1.8)
highline (1.6.18)
hike (1.2.2)
+ hirb (0.7.1)
+ hirb-unicode (0.0.5)
+ hirb (~> 0.5)
+ unicode-display_width (~> 0.1.1)
http_parser.rb (0.5.3)
httparty (0.8.3)
multi_json (~> 1.0)
@@ -172,7 +176,6 @@ GEM
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
- map_by_method (0.8.3)
meta_request (0.2.3)
rack-contrib
railties
@@ -186,6 +189,7 @@ GEM
multi_xml (0.5.3)
mysql2 (0.3.11)
nested_form (0.3.2)
+ net-http-spy (0.2.1)
net-scp (1.1.0)
net-ssh (>= 2.6.5)
net-sftp (2.1.1)
@@ -262,6 +266,7 @@ GEM
ffi (>= 0.5.0)
rdoc (3.12.2)
json (~> 1.4)
+ redcarpet (2.2.2)
ref (1.0.4)
remotipart (1.0.5)
rspec (2.13.0)
@@ -279,6 +284,7 @@ GEM
rspec-core (~> 2.13.0)
rspec-expectations (~> 2.13.0)
rspec-mocks (~> 2.13.0)
+ ruby-graphviz (1.0.8)
ruby_gntp (0.3.4)
rvm-capistrano (1.3.0)
capistrano (>= 2.0.0)
@@ -305,6 +311,7 @@ GEM
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.7)
+ state_machine (1.2.0)
therubyracer (0.11.4)
libv8 (~> 3.11.8.12)
ref
@@ -317,6 +324,7 @@ GEM
uglifier (2.0.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
+ unicode-display_width (0.1.1)
warden (1.2.1)
rack (>= 1.0)
what_methods (1.0.1)
@@ -331,9 +339,10 @@ PLATFORMS
ruby
DEPENDENCIES
- aasm
+ awesome_print
better_errors
binding_of_caller
+ bond
bootstrap-datepicker-rails!
bootstrap-sass (~> 2.1.1.0)
capistrano
@@ -344,6 +353,7 @@ DEPENDENCIES
client_side_validations
cocaine!
coffee-rails (~> 3.2.1)
+ crack
database_cleaner
debugger
devise
@@ -352,19 +362,19 @@ DEPENDENCIES
faker
figaro
font-awesome-sass-rails
- formtastic
guard-livereload
guard-rspec
haml
+ hirb-unicode
jquery-rails (~> 2.1.0)
jquery-ui-rails
kaminari
launchy
libv8 (~> 3.11.8)
- map_by_method
meta_request
modernizr-rails
mysql2
+ net-http-spy
paper_trail
paperclip
poltergeist
@@ -373,13 +383,16 @@ DEPENDENCIES
rb-fchange
rb-fsevent
rb-inotify
+ redcarpet
rspec-rails (~> 2.0)
+ ruby-graphviz
ruby_gntp
rvm-capistrano
sass-rails (~> 3.2.3)
shoulda
simplecov
sqlite3
+ state_machine
therubyracer
uglifier (>= 1.0.3)
what_methods
diff --git a/app/assets/stylesheets/reuman.css.scss b/app/assets/stylesheets/reuman.css.scss
index 89e6d264..6c9baafd 100644
--- a/app/assets/stylesheets/reuman.css.scss
+++ b/app/assets/stylesheets/reuman.css.scss
@@ -70,8 +70,8 @@ footer.container { font-size:0.8em;
.field_with_errors { color:#8A1F11; display:inline;
input { background-color:#FBC2C4; }}
-#error_explanation { background-color:#FBC2C4; border:2px solid #8A1F11; font-weight:200; margin:10px 0; padding:7px; text-align:left;
- h2 { text-align:left; font-weight:normal; margin:2px -7px 10px -7px; padding:5px 5px 5px 15px; font-size:1.6em; background-color:#FBC2C4; color:#8A1F11; }
+#error_explanation { background-color:#f2dede; border:2px solid #eed3d7; color:#b94a48; font-weight:200; margin:10px 0; padding:7px; text-align:left; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px;
+ h2 { text-align:left; font-weight:normal; margin:2px -7px 10px -7px; padding:5px 5px 5px 15px; font-size:1.6em; background-color:#f2dede; color:#b94a48; }
p { color:#333; font-size:1.4em; font-weight:bold; margin-bottom:0; padding:5px; }
ul { margin:1em 3em; }
ul li { font-size:1.2em; list-style:square; }
diff --git a/app/controllers/applicants/academic_records_controller.rb b/app/controllers/applicants/academic_records_controller.rb
index e6519a8e..011fba47 100644
--- a/app/controllers/applicants/academic_records_controller.rb
+++ b/app/controllers/applicants/academic_records_controller.rb
@@ -1,5 +1,5 @@
class Applicants::AcademicRecordsController < ApplicationController
- before_filter :authenticate_applicant!
+ before_filter [:authenticate_applicant!, :set_state]
def edit
current_applicant.records.build unless current_applicant.records.count > 0
@@ -10,7 +10,9 @@ def edit
def update
if current_applicant.update_attributes params[:applicant]
- redirect_to applicants_records_url
+ current_applicant.can_complete_academic_info? ? current_applicant.complete_academic_info : current_applicant.incomplete_academic_info
+
+ redirect_to current_applicant.redirect_url
else
render :edit
end
diff --git a/app/controllers/applicants/recommenders_controller.rb b/app/controllers/applicants/recommenders_controller.rb
index fa994c12..0cda2514 100644
--- a/app/controllers/applicants/recommenders_controller.rb
+++ b/app/controllers/applicants/recommenders_controller.rb
@@ -24,12 +24,14 @@ def update
if recommender_data[1]
params[:applicant][:recommenders_attributes] = recommender_data[1]
if @applicant.update_attributes(params[:applicant])
- redirect_to applicants_recommenders_url
+ current_applicant.can_complete_recommender_info? ? current_applicant.complete_recommender_info : current_applicant.incomplete_recommender_info
+ redirect_to current_applicant.redirect_url
else
render :edit
end
else
- redirect_to applicants_recommenders_url
+ current_applicant.can_complete_recommender_info? ? current_applicant.complete_recommender_info : current_applicant.incomplete_recommender_info
+ redirect_to current_applicant.redirect_url
end
end
diff --git a/app/controllers/applicants/registrations_controller.rb b/app/controllers/applicants/registrations_controller.rb
index ad1b3d4d..b171b9df 100644
--- a/app/controllers/applicants/registrations_controller.rb
+++ b/app/controllers/applicants/registrations_controller.rb
@@ -1,7 +1,9 @@
class Applicants::RegistrationsController < Devise::RegistrationsController
-
+ before_filter :auth, :only => [:status, :update, :submit]
+
# GET /resource/edit
def edit
+ @applicant.set_state
render :edit
end
@@ -25,7 +27,9 @@ def update
set_flash_message :notice, :updated
# Sign in the applicant bypassing validation in case his password changed
sign_in @applicant, :bypass => true
- redirect_to edit_applicant_registration_url
+ @applicant.set_state
+
+ redirect_to @applicant.redirect_url
else
render "edit"
end
@@ -35,11 +39,28 @@ def update
# Show view of profile
def status
@applicant = current_applicant
- @applicant.validates_application_completeness if @applicant
+ end
+
+ # GET /resource/submit
+ # check that app is complete and mark as submitted, trigger confirmation email and recommendation request.
+ def submit
+
+ if current_applicant && current_applicant.submit_application && current_applicant.errors.empty?
+ flash[:success] = "Application submitted."
+ redirect_to current_applicant.redirect_url
+ else
+ flash[:error] = "You cannot submit your application until it is complete."
+ redirect_to current_applicant.redirect_url
+ end
end
private
+ def auth
+ :authenticate_applicant!
+ redirect_to new_applicant_session_url unless current_applicant
+ end
+
# https://github.com/plataformatec/devise/wiki/How-To%3a-Allow-users-to-edit-their-account-without-providing-a-password
# check if we need password to update applicant data
# ie if password or email was changed
@@ -47,5 +68,5 @@ def status
def needs_password?(applicant, params)
applicant.email != params[:applicant][:email] || !params[:applicant][:password].blank?
end
-
+
end
\ No newline at end of file
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 03ba5fdd..92983100 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -18,4 +18,9 @@ def log_x_forwarded_by
Rails.logger.info "REMOTE IP: " + request.env["HTTP_X_FORWARDED_FOR"].split(',').first
end
end
+
+ def set_state
+ current_applicant.set_state
+ end
+
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 68846aa9..9e516148 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -93,18 +93,25 @@ def status_error_messages!
def application_status
- if current_applicant.errors.empty?
+ case current_applicant.state
+ when 'completed_recommender_info'
+ status = "Ready to submit"
+ message = "
Your application is ready to submit. Please review your data and click the #{link_to "submit button", submit_application_path } when you are ready to submit your application.
"
+ when 'submitted'
+ status = "Application submitted"
+ message = "Your application has been submitted and your recommendation request has been sent. Although you will be updated by email, you can continue to monitor this page for status updates or to make modifications to your application before the deadline.
"
+ when 'completed'
status = "Complete"
message = "Your application is complete. Please review your data and #{link_to "logout", destroy_applicant_session_path, :method => :delete} when finished. You will also recieve an email confirming that your application was submitted.
"
else
status = "Incomplete"
- message = "Your application is incomplete due to the errors mentioned above. It will not be accepted until all of the necessary data has been added.
"
- message += "Please go back and #{link_to 'edit', edit_applicant_registration_path } your application.
"
+ message = "Your application is incomplete due to the errors mentioned above. It will not be accepted until all of the necessary data has been added."
+ message += " Please go back and #{link_to 'edit', edit_applicant_registration_path } your application.
"
end
html = <<-HTML
-
#{status.upcase}
+ #{status}
#{message}
HTML
diff --git a/app/mailers/notification.rb b/app/mailers/notification.rb
index e52b268f..466575f5 100644
--- a/app/mailers/notification.rb
+++ b/app/mailers/notification.rb
@@ -1,7 +1,6 @@
class Notification < ActionMailer::Base
- default from: "Bioengineering Institute of California "
- default subject: "14th UC Systemwide Bioengineering Symposium, June 19-21"
- default url: "http://bic.ucop.edu/2013"
+ default from: "Bioengineering Institute of California recommedation.applicant.email, :subject => "REU recommendation request for #{recommedation.applicant.name}")
+ @recommendation = recommedation
+ mail(:to => recommedation.recommender.email, :subject => "REU recommendation request for #{recommedation.applicant.name}")
end
def recommendation_follow_up_request(recommedation)
- mail(:to => recommedation.applicant.email, :subject => "REU follow-up recommendation request for #{recommedation.applicant.name}")
- end
+ @recommendation = recommedation
+ mail(:to => recommedation.recommender.email, :subject => "REU follow-up recommendation request for #{recommedation.applicant.name}")
+ end
+
+ def application_submitted(applicant)
+ @applicant = applicant
+ mail(:to => applicant.email, :subject => "REU application received for #{applicant.name}")
+ end
+
end
diff --git a/app/models/academic_record.rb b/app/models/academic_record.rb
index 6498a6d6..5a16d867 100644
--- a/app/models/academic_record.rb
+++ b/app/models/academic_record.rb
@@ -8,4 +8,10 @@ class AcademicRecord < ActiveRecord::Base
validates :finish, :presence => true
validates :gpa, :presence => true
validates :gpa_range, :presence => true
+
+ def to_s
+ record = "#{self.start.strftime("%Y.%m")} - #{self.finish.strftime("%Y.%m")} #{self.academic_level} studying #{self.degree} at #{self.university}"
+ record << "\n#{Markdown.render "GPA Comment: " + self.gpa_comment}" if self.gpa_comment
+ end
+
end
diff --git a/app/models/address.rb b/app/models/address.rb
index a8a0e144..54f9b0a6 100644
--- a/app/models/address.rb
+++ b/app/models/address.rb
@@ -2,9 +2,14 @@ class Address < ActiveRecord::Base
attr_accessible :address, :address2, :city, :country, :label, :permanent, :state, :zip
belongs_to :applicant, :class_name => "Applicant", :foreign_key => "applicant_id"
+ validates :address, :presence => true
validates :label, :inclusion => { :in => %w( Home School Other ) }
validates :city, :presence => true
validates :state, :presence => true
validates :zip, :presence => true
validates :permanent, :inclusion => { :in => ["Yes", "No"] }
+
+ def to_s
+ "#{self.address},#{' ' + self.address2 + ',' if self.address2} #{self.city}, #{self.state} #{self.zip}"
+ end
end
diff --git a/app/models/applicant.rb b/app/models/applicant.rb
index 0b27846d..b1e66d91 100644
--- a/app/models/applicant.rb
+++ b/app/models/applicant.rb
@@ -22,30 +22,197 @@ class Applicant < ActiveRecord::Base
# validates_presence_of :records, :if => :academic_records_controller?
# validate :must_have_academic_record, :if => :academic_records_controller?
-
-
+ scope :applied, -> { with_state(:applied) }
+ scope :personal_info, -> { with_state(:personal_info) }
+ scope :academic_info, -> { with_state(:academic_info) }
+ scope :recommended, -> { with_state(:recommended) }
+
+ scope :complete, -> { with_state(:complete) }
+ scope :incomplete, -> { with_state(:incomplete) }
+
+ scope :withdrawn, -> { with_state(:withdrawn) }
+ scope :accepted, -> { with_state(:accepted) }
+ scope :rejected, -> { with_state(:rejected) }
rails_admin do
label "List of applicants"
end
-
+
+ state_machine :initial => :applied do
+
+ # confirmed
+ # personal info
+ # location_added
+ # peresonal statement
+ # academic info
+ # academic record
+ # awards
+ # cpu_skills
+ # lab_skills
+ # recommender
+ # submit
+ # recommended (before/aft deadline)
+ # complete/incomplete (after deadline)
+ # withdrawn/accepted/rejected
+
+ # StateMachine State method definitions. Here we provide a unique
+ # redirect_url for each state
+ state :applied do
+ def redirect_url
+ Rails.application.routes.url_helpers.edit_applicant_registration_url(:only_path => true)
+ end
+ end
+
+ state :completed_personal_info do
+ def redirect_url
+ Rails.application.routes.url_helpers.applicants_records_url(:only_path => true)
+ end
+ end
+
+ state :completed_academic_info do
+ def redirect_url
+ Rails.application.routes.url_helpers.applicants_recommenders_url(:only_path => true)
+ end
+ end
+
+ state :completed_recommender_info do
+ def redirect_url
+ Rails.application.routes.url_helpers.applicant_status_url(:only_path => true)
+ end
+ end
+
+ state :submitted do
+ def redirect_url
+ Rails.application.routes.url_helpers.applicant_status_url(:only_path => true)
+ end
+ end
+
+
+ # StateMachine Event transitions
+ event :complete_personal_info do
+ transition all => :completed_personal_info, :if => lambda { |applicant| applicant.validates_personal_info }
+ end
+ event :incomplete_personal_info do
+ transition all => :applied
+ end
+
+
+ event :complete_academic_info do
+ transition all => :completed_academic_info, :if => lambda { |applicant| applicant.validates_academic_info && applicant.validates_personal_info }
+ end
+ event :incomplete_academic_info do
+ transition all => :completed_personal_info, :if => lambda { |applicant| !applicant.validates_academic_info && !applicant.validates_personal_info }
+ end
+
+
+ event :complete_recommender_info do
+ transition all => :completed_recommender_info, :if => lambda { |applicant| applicant.validates_academic_info && applicant.validates_personal_info && applicant.validates_recommender_info }
+ end
+ event :incomplete_recommender_info do
+ transition all => :completed_academic_info, :if => lambda { |applicant| !applicant.validates_recommender_info }
+ end
+
+
+ event :submit_application do
+ transition all => :submitted, :if => lambda { |applicant| applicant.validates_application_completeness }
+ end
+
+ after_transition :on => :submit_application, :do => :submit_application_callbacks
+
+ event :unsubmit_application do
+ transition all => :completed_recommender_info, :if => lambda { |applicant| !applicant.validates_application_completeness }
+ end
+
+ after_transition :on => :unsubmit_application, :do => lambda { |applicant| applicant.update_attribute :submitted_at, nil }
+
+ event :recommendation_recieved do
+ transition :submitted => :complete, :if => lambda { |applicant| applicant.submitted? }
+ end
+
+ after_transition :on => :recommendation_recieved, :do => lambda { |applicant| applicant.update_attribute :completed_at, Time.now }
+
+ event :missed_deadline do
+ transition all => :incomplete
+ end
+
+ event :withdraw do
+ transition all => :withdrawn
+ end
+
+ event :reject do
+ transition all => :rejected
+ end
+
+ event :accept do
+ transition all => :accepted
+ end
+
+
+ end
+
+ def address
+ self.addresses.first
+ end
def name
name = ""
name += "#{self.first_name} #{self.last_name}"
end
+ def recommendation
+ self.recommendations.first
+ end
- def validates_application_completeness
- validates_presence_of :phone, :on => :update, :message => "can't be blank"
+ def recommender
+ self.recommenders.first
end
- def must_have_academic_record
+ def validates_personal_info
+ validates_presence_of :addresses, :message => "can't be blank. Please add at least one address to your profile."
+ validates_presence_of :phone, :message => "can't be blank. Please add at least one phone number to your profile."
+ validates_presence_of :statement, :message => "can't be blank. Please add at least one phone number to your profile."
+ return true if self.errors.empty?
+ end
+ def validates_academic_info
+ validates_presence_of :records, :message => "can't be blank. Please add at least one academic record."
+ return true if self.errors.empty?
+ end
+
+ def validates_recommender_info
+ validates_presence_of :recommenders, :message => "can't be blank. Please add at least one recommender."
+ return true if self.errors.empty?
+ end
+
+ def validates_application_completeness
+ validates_personal_info && validates_academic_info && validates_recommender_info
end
- def academic_records_controller?
+ def set_state
+ case
+ when !self.validates_personal_info
+ self.incomplete_personal_info
+ when !self.validates_academic_info
+ self.incomplete_academic_info
+ when !self.validates_recommender_info
+ self.incomplete_recommender_info
+ when !self.submitted_at
+ self.complete_recommender_info
+ when !self.completed_at
+ self.recommendation_recieved
+ else
+ self.state
+ end
+ end
+
+ def submit_application_callbacks
+ self.update_attribute :submitted_at, Time.now
+
+ Notification.application_submitted(self).deliver
+ self.recommendations.each do |recommendation|
+ Notification.recommendation_request(recommendation).deliver
+ end
end
end
diff --git a/app/models/recommendation.rb b/app/models/recommendation.rb
index 17eac319..c3c71ea8 100644
--- a/app/models/recommendation.rb
+++ b/app/models/recommendation.rb
@@ -12,6 +12,7 @@ class Recommendation < ActiveRecord::Base
validates_presence_of :applicant
validates_presence_of :recommender
+ before_create :make_token
after_destroy :remove_orphaned_recommenders
private
@@ -22,8 +23,11 @@ def remove_orphaned_recommenders
end
def make_token
- self.token = "#{Digest::MD5.hexdigest(Time.now.to_s.split(//).sort_by{rand}.join)}-#{Digest::MD5.hexdigest(self.body)}"
+ self.token = "#{Digest::MD5.hexdigest(Time.now.to_s.split(//).sort_by{rand}.join)}-#{Digest::MD5.hexdigest((Time.now - 30.days).to_s.split(//).sort_by{rand}.join)}"
self.token_created_at = Time.now
end
+ def received?
+ true
+ end
end
diff --git a/app/models/recommender.rb b/app/models/recommender.rb
index 33717c83..3cfc3530 100644
--- a/app/models/recommender.rb
+++ b/app/models/recommender.rb
@@ -18,6 +18,11 @@ def name
name += "#{self.first_name} #{self.last_name}"
end
+ def to_s
+ recommender = "#{self.name} (#{self.email}), #{self.title}, #{self.department}, #{self.organization}"
+ #recommender << " [RECIEVED]" # self.recommendations.received? ? " [RECIEVED]" : " [NOT RECIEVED]"
+ end
+
private
# parse params for existing recommenders and add to array. returns
diff --git a/app/views/applicants/academic_records/_record_fields.html.erb b/app/views/applicants/academic_records/_record_fields.html.erb
index 930d5871..bf0a28f7 100644
--- a/app/views/applicants/academic_records/_record_fields.html.erb
+++ b/app/views/applicants/academic_records/_record_fields.html.erb
@@ -25,7 +25,7 @@
- <%= f.text_field :finish, 'data-behaviour' => 'datepicker', :placeholder => 'Finish' %>
+ <%= f.text_field :finish, 'data-behaviour' => 'datepicker', :placeholder => 'Finish or Expected Finish' %>
diff --git a/app/views/applicants/registrations/status.html.erb b/app/views/applicants/registrations/status.html.erb
index fd18eaa3..de720e2f 100644
--- a/app/views/applicants/registrations/status.html.erb
+++ b/app/views/applicants/registrations/status.html.erb
@@ -1,35 +1,71 @@
-
-
-
-
-
- <%= status_error_messages! %>
- <%= application_status %>
-
-
Current Application Status for <%= @applicant.name %>
+
+
+ <%= status_error_messages! if current_applicant %>
+ <%= application_status if current_applicant %>
+
-
-
- - Email <%= @applicant.email %>
- - Phone <%= @applicant.phone %>
-
-
-
+
+
+
+
+
+
Contact Info
+
+ - Name <%= current_applicant.name %>
+ - Email <%= current_applicant.email %>
+ - Phone <%= current_applicant.phone %>
+ - Address <%= current_applicant.address %>
+
+
+
+
+
+
+
Personal Statement
+ <%= raw Markdown.render(current_applicant.statement) %>
+
+
-
-
Courses Completed and Grades Assigned
-
+
-
+
+
+
Recommender Info
+
+ <%- current_applicant.recommenders.each do |recommender| -%>
+ - <%= raw recommender %>
+ <%- end -%>
+
+
+
-
-
<%= link_to "Edit Application Data", edit_applicant_registration_path(current_applicant), :class => "btn btn-success" %> Changed your mind? <%= link_to "Delete your aapplication", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete, :class => "btn btn-mini btn-danger" %>.
+
+
+
Academic Info
+
+ <%- current_applicant.records.each do |record| -%>
+ - <%= raw record %>
+ <%- end -%>
- You can <%= link_to "logout", destroy_applicant_session_path, :method => :delete %> and continue you application at anytime, however, it will not be accepted until all the above data has been submitted.
-
-
-
+ <%- current_applicant.awards.each do |award| -%>
+
Award <%= award %>
+ <%- end -%>
+
+
Computer Skills <%= current_applicant.cpu_skills %>
+
Laboratory Skills <%= current_applicant.lab_skills %>
+
+
+
+
-
+ <%= content_tag :div, content_tag(:p, link_to("Submit Application", submit_application_path, :class => "btn btn-success")), :class => 'inner' if current_applicant.state == 'completed_recommender_info' %>
+
+
+
+
<%= link_to "Edit Application Data", edit_applicant_registration_path(current_applicant), :class => "btn btn-mini btn-info" %> Changed your mind? <%= link_to "Delete your aapplication", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete, :class => "btn btn-mini btn-danger" %>.
+
You can <%= link_to "logout", destroy_applicant_session_path, :method => :delete %> and continue you application at anytime, however, it will not be accepted until all the above data has been submitted.
+
+
+
\ No newline at end of file
diff --git a/app/views/notifications/application_submitted.text.erb b/app/views/notifications/application_submitted.text.erb
new file mode 100644
index 00000000..67b10d6e
--- /dev/null
+++ b/app/views/notifications/application_submitted.text.erb
@@ -0,0 +1 @@
+This is a confirmation email for <%= @applicant.name + "( " + @applicant.email + " )" %>. Your application has been submitted and a recommendation request has been sent to the email address(es) of your faculty recommender(s).
\ No newline at end of file
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 34b46aed..1189d426 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -4,7 +4,7 @@
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class with default "from" parameter.
- config.mailer_sender = "be-uginfo@bioeng.ucsd.edu"
+ config.mailer_sender = "demo@reumanager.com"
# Configure the class responsible to send e-mails.
# config.mailer = "Devise::Mailer"
diff --git a/config/initializers/red_carpet.rb b/config/initializers/red_carpet.rb
new file mode 100644
index 00000000..b8bfb5bc
--- /dev/null
+++ b/config/initializers/red_carpet.rb
@@ -0,0 +1 @@
+Markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, :autolink => true, :space_after_headers => true)
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 179c14ca..c7ecec08 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -2,4 +2,3 @@
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
en:
- hello: "Hello world"
diff --git a/db/migrate/20120923015419_devise_create_applicants.rb b/db/migrate/20120923015419_devise_create_applicants.rb
index 1849aa1f..c468df63 100644
--- a/db/migrate/20120923015419_devise_create_applicants.rb
+++ b/db/migrate/20120923015419_devise_create_applicants.rb
@@ -21,6 +21,7 @@ def change
t.text :statement
t.datetime :submitted_at
+ t.datetime :completed_at
## Database authenticatable
t.string :email, :null => false, :default => ""
@@ -54,8 +55,8 @@ def change
## Token authenticatable
t.string :authentication_token
- ## ActsAsStateMachine State
- t.string :aasm_state
+ ## state_machine state
+ t.string :state
t.timestamps
end
diff --git a/db/migrate/20121217025345_create_recommendations.rb b/db/migrate/20121217025345_create_recommendations.rb
index 63e95c6d..c711c837 100644
--- a/db/migrate/20121217025345_create_recommendations.rb
+++ b/db/migrate/20121217025345_create_recommendations.rb
@@ -7,7 +7,7 @@ def change
t.string :undergraduate_institution
t.text :body
t.string :token
- t.datetime :toke_created_at
+ t.datetime :token_created_at
t.integer :applicant_id
t.integer :recommender_id
diff --git a/db/schema.rb b/db/schema.rb
index 5bde7cd8..4baf4456 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -55,6 +55,7 @@
t.text "cpu_skills"
t.text "statement"
t.datetime "submitted_at"
+ t.datetime "completed_at"
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false
t.string "reset_password_token"
@@ -73,7 +74,7 @@
t.string "unlock_token"
t.datetime "locked_at"
t.string "authentication_token"
- t.string "aasm_state"
+ t.string "state"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
@@ -113,7 +114,7 @@
t.string "undergraduate_institution"
t.text "body"
t.string "token"
- t.datetime "toke_created_at"
+ t.datetime "token_created_at"
t.integer "applicant_id"
t.integer "recommender_id"
t.datetime "created_at", :null => false
diff --git a/spec/controllers/applicants/recommenders_controller_spec.rb b/spec/controllers/applicants/recommenders_controller_spec.rb
index 9bdb7875..0a1e6126 100644
--- a/spec/controllers/applicants/recommenders_controller_spec.rb
+++ b/spec/controllers/applicants/recommenders_controller_spec.rb
@@ -57,8 +57,8 @@ def confirm_and_login(applicant=nil)
put :update, "applicant"=> { "recommenders_attributes" => { "0" => @recommender_attributes } }
end
- it "redirects to the applicant recommender page" do
- expect(response).to redirect_to applicants_recommenders_url
+ it "redirects to the applicant edit page" do
+ expect(response).to redirect_to edit_applicant_registration_url
end
it "creates a recommender object for the authenticated applicant using the provided attributes" do
@@ -161,8 +161,8 @@ def confirm_and_login(applicant=nil)
put :update, "applicant"=> { "recommenders_attributes" => { "0" => @recommender_attributes } }
end
- it "redirects to the applicant recommender page" do
- expect(response).to redirect_to applicants_recommenders_url
+ it "redirects to the applicant edit page" do
+ expect(response).to redirect_to edit_applicant_registration_url
end
it "updates the existing recommender using the provided attributes" do
@@ -206,8 +206,8 @@ def confirm_and_login(applicant=nil)
put :update, "applicant"=> { "recommenders_attributes" => { "0" => @recommender_attributes } }
end
- it "redirects to the applicant recommender page" do
- expect(response).to redirect_to applicants_recommenders_url
+ it "redirects to the applicant edit page" do
+ expect(response).to redirect_to edit_applicant_registration_url
end
it "creates a recommender object for the authenticated applicant using the provided attributes" do
@@ -223,8 +223,8 @@ def confirm_and_login(applicant=nil)
put :update, "applicant"=> { "recommenders_attributes" => { "0" => @recommender_attributes } }
end
- it "re-renders the edit page" do
- expect(response).to redirect_to applicants_recommenders_url
+ it "redirects to the applicant edit page" do
+ expect(response).to redirect_to edit_applicant_registration_url
end
it "replaces the invalid attribute (and others) with those from the existing recommender" do
@@ -247,8 +247,8 @@ def confirm_and_login(applicant=nil)
put :update, "applicant"=> { "recommenders_attributes" => { "0" => @recommender0_attributes, "1" => @recommender1_attributes } }
end
- it "redirects to the applicant recommender page" do
- expect(response).to redirect_to applicants_recommenders_url
+ it "redirects to the applicant edit page" do
+ expect(response).to redirect_to edit_applicant_registration_url
end
it "creates a recommender object for the authenticated applicant using the provided attributes" do
diff --git a/spec/factories/academic_records.rb b/spec/factories/academic_records.rb
new file mode 100644
index 00000000..5950eaaf
--- /dev/null
+++ b/spec/factories/academic_records.rb
@@ -0,0 +1,14 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :academic_record do
+ university { %w{ UC\ San Diego UCLA UCSF UC\ Irvine UC\ Riverside Harvard Stanford }[rand(7)] }
+ degree { %w{ Bioengineering Biology Chemistry Bioinformatics Biotechnology Medicine} }
+ start { Date.civil(2008,9,20) }
+ finish { Date.civil(2013,1,20) }
+ gpa { rand(0.5..4.0) }
+ gpa_range { 4.0 }
+ academic_level { %w{ Freshman Sophomore Junior Senior }[rand(4)] }
+ gpa_comment { Faker::Lorem.sentences(3).join(' ') }
+ end
+end
\ No newline at end of file
diff --git a/spec/factories/addresses.rb b/spec/factories/addresses.rb
new file mode 100644
index 00000000..34611ad1
--- /dev/null
+++ b/spec/factories/addresses.rb
@@ -0,0 +1,14 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :address do
+ address { Faker::Address.street_address }
+ address2 { Faker::Address.secondary_address }
+ city { Faker::Address.city }
+ state { Faker::Address.state_abbr }
+ zip { Faker::Address.zip_code }
+
+ label { %w{ Home School Other }[rand(3)] }
+ permanent { 'Yes' }
+ end
+end
diff --git a/spec/factories/awards.rb b/spec/factories/awards.rb
new file mode 100644
index 00000000..90507c4d
--- /dev/null
+++ b/spec/factories/awards.rb
@@ -0,0 +1,9 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :award do
+ title { %w{ UC\ San Diego UCLA UCSF UC\ Irvine UC\ Riverside Harvard Stanford }[rand(7)] }
+ date { Date.civil(2009,2,4) }
+ description { Faker::Lorem.sentences(2).join(' ') }
+ end
+end
\ No newline at end of file
diff --git a/spec/factories/recommendations.rb b/spec/factories/recommendations.rb
index fdb230a2..0639c2d9 100644
--- a/spec/factories/recommendations.rb
+++ b/spec/factories/recommendations.rb
@@ -8,7 +8,9 @@
overall_promise { ['Top 1%', 'Top 5%', 'Top 10%', 'Top 25%', 'average', 'below average'][rand(6)] }
undergraduate_institution { ['Yes', 'No', nil][rand(3)] }
- applicant_id { FactoryGirl.create(:applicant).id }
- recommender_id { FactoryGirl.create(:recommender).id }
+ factory :recommendation_with_associations do
+ applicant_id { FactoryGirl.create(:applicant).id }
+ recommender_id { FactoryGirl.create(:recommender).id }
+ end
end
end
diff --git a/spec/models/applicant_spec.rb b/spec/models/applicant_spec.rb
index 70a31af7..7f2eaf84 100644
--- a/spec/models/applicant_spec.rb
+++ b/spec/models/applicant_spec.rb
@@ -2,4 +2,123 @@
describe Applicant do
+ describe 'states' do
+ before { @applicant = FactoryGirl.create(:applicant) }
+
+ it "starts in the 'applied' state" do
+ expect(@applicant.state).to eq('applied')
+ end
+
+ it "transitions to the 'completed_personal_info' state with valid personal info" do
+ @applicant.update_attributes( addresses_attributes: {'0' => FactoryGirl.attributes_for(:address)}, statement: Faker::Lorem.sentences(4).join(' '))
+ @applicant.set_state
+
+ expect(@applicant.state).to eq('completed_personal_info')
+ end
+
+ it "transitions back to 'applied' when a required personal info attribute is removed" do
+ @applicant.update_attributes( addresses_attributes: {'0' => FactoryGirl.attributes_for(:address)}, statement: nil)
+ @applicant.set_state
+
+ expect(@applicant.state).to eq('applied')
+ end
+
+ it "transitions to the 'completed_academic_info' state with valid personal info & valid academic record info" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) },
+ records_attributes: { '0' => FactoryGirl.attributes_for(:academic_record) }
+ @applicant.set_state
+
+ expect(@applicant.state).to eq('completed_academic_info')
+ end
+
+ it "transitions back to 'completed_personal_info' when a required academic info attribute is removed" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) }
+ @applicant.set_state
+
+ expect(@applicant.state).to eq('completed_personal_info')
+ end
+
+ it "transitions to the 'completed_recommender_info' state with valid personal info, valid academic record info, & valid recommender info" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) },
+ records_attributes: { '0' => FactoryGirl.attributes_for(:academic_record) },
+ recommenders_attributes: { '0' => FactoryGirl.attributes_for(:recommender) }
+ @applicant.set_state
+
+ expect(@applicant.state).to eq('completed_recommender_info')
+ end
+
+ it "transitions back to 'completed_academic_info' when a required recommender info attribute is removed" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) },
+ records_attributes: { '0' => FactoryGirl.attributes_for(:academic_record) }
+ @applicant.set_state
+
+ expect(@applicant.state).to eq('completed_academic_info')
+ end
+
+ it "transitions to the 'submitted' state with valid attributes when the .submit_application method is called" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) },
+ records_attributes: { '0' => FactoryGirl.attributes_for(:academic_record) },
+ recommenders_attributes: { '0' => FactoryGirl.attributes_for(:recommender) }
+ @applicant.submit_application
+
+ expect(@applicant.state).to eq('submitted')
+ end
+
+ it "sends a recommendation request when the application is submitted" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) },
+ records_attributes: { '0' => FactoryGirl.attributes_for(:academic_record) },
+ recommenders_attributes: { '0' => FactoryGirl.attributes_for(:recommender) }
+ @applicant.submit_application
+ confirmation_mail = ActionMailer::Base.deliveries[-2]
+
+ expect(confirmation_mail.to.first).to eq(@applicant.email)
+ expect(confirmation_mail.subject).to eq("REU application received for #{@applicant.name}")
+ end
+
+ it "sends a confirmation to the applicant when the application is submitted" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) },
+ records_attributes: { '0' => FactoryGirl.attributes_for(:academic_record) },
+ recommenders_attributes: { '0' => FactoryGirl.attributes_for(:recommender) }
+ @applicant.submit_application
+
+ expect(last_email.to.first).to eq(@applicant.recommender.email)
+ expect(last_email.subject).to eq("REU recommendation request for #{@applicant.name}")
+ end
+
+ it "transitions back to 'completed_academic_info' and resets submitted_at to nil when a recommender has been removed or modified on a submitted application" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) },
+ records_attributes: { '0' => FactoryGirl.attributes_for(:academic_record) },
+ recommenders_attributes: { '0' => FactoryGirl.attributes_for(:recommender) }
+ @applicant.submit_application
+ expect(@applicant.state).to eq('submitted')
+
+ @applicant.recommenders.first.destroy
+ @applicant.reload
+ @applicant.set_state
+
+ expect(@applicant.state).to eq('completed_academic_info')
+ end
+
+ it "transitions to the 'complete' state with valid attributes and a valid recommendation" do
+ @applicant.update_attributes statement: Faker::Lorem.sentences(4).join(' '),
+ addresses_attributes: { '0' => FactoryGirl.attributes_for(:address) },
+ records_attributes: { '0' => FactoryGirl.attributes_for(:academic_record) },
+ recommenders_attributes: { '0' => FactoryGirl.attributes_for(:recommender) }
+ @applicant.submit_application
+ @applicant.recommendation.update_attributes FactoryGirl.attributes_for(:recommendation, recommender_id: @applicant.recommender.id)
+ @applicant.set_state
+
+ expect(@applicant.state).to eq('complete')
+ end
+
+ end
+
end
\ No newline at end of file
diff --git a/spec/models/recommendation_spec.rb b/spec/models/recommendation_spec.rb
index 6bdee39c..1f71195e 100644
--- a/spec/models/recommendation_spec.rb
+++ b/spec/models/recommendation_spec.rb
@@ -12,25 +12,25 @@
it { should belong_to :recommender }
it "is valid with the required attributes" do
- recommendation = FactoryGirl.create(:recommendation)
+ recommendation = FactoryGirl.create(:recommendation_with_associations)
expect(recommendation).to be_valid
end
%w{ body known_applicant_for known_capacity overall_promise}.each do |m|
it "is INVALID without the required attribute '#{m}'" do
- recommendation = FactoryGirl.create(:recommendation, m.to_sym => nil)
+ recommendation = FactoryGirl.create(:recommendation_with_associations, m.to_sym => nil)
expect(recommendation).to be_invalid
end
end
%w{ applicant_id recommender_id }.each do |relationship|
it 'is INVALID without the required #{relationship} relationship' do
- expect(FactoryGirl.build(:recommendation, relationship.to_sym => nil)).to be_invalid
+ expect(FactoryGirl.build(:recommendation_with_associations, relationship.to_sym => nil)).to be_invalid
end
end
it 'removes the orphaned recommender when deleted' do
- recommendation = FactoryGirl.create(:recommendation)
+ recommendation = FactoryGirl.create(:recommendation_with_associations)
recommender = recommendation.recommender
recommendation.destroy