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