diff --git a/app/controllers/concerns/invitation_controller_concerns.rb b/app/controllers/concerns/invitation_controller_concerns.rb
new file mode 100644
index 000000000..18766f107
--- /dev/null
+++ b/app/controllers/concerns/invitation_controller_concerns.rb
@@ -0,0 +1,35 @@
+module InvitationControllerConcerns
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :set_invitation
+
+ include InstanceMethods
+ end
+
+ module InstanceMethods
+
+ def accept
+ if @invitation.attending.eql? true
+ redirect_to root_path, notice: t("messages.already_rsvped")
+
+ elsif has_remaining_seats?(@invitation)
+ @invitation.update_attribute(:attending, true)
+
+ redirect_to root_path, notice: t("messages.accepted_invitation",
+ name: @invitation.member.name)
+ else
+ redirect_to root_path, notice: t("messages.no_available_seats")
+ end
+ end
+
+ def reject
+ if @invitation.attending.eql? false
+ redirect_to root_path, notice: t("messages.not_attending_already")
+ else
+ @invitation.update_attribute(:attending, false)
+ redirect_to root_path, notice: t("messages.rejected_invitation", name: @invitation.member.name)
+ end
+ end
+ end
+end
diff --git a/app/controllers/course/invitation_controller.rb b/app/controllers/course/invitation_controller.rb
index af424ed20..0234657bc 100644
--- a/app/controllers/course/invitation_controller.rb
+++ b/app/controllers/course/invitation_controller.rb
@@ -1,22 +1,11 @@
class Course::InvitationController < ApplicationController
+ include InvitationControllerConcerns
- def accept
+ private
+ def set_invitation
@invitation = CourseInvitation.find_by_token(params[:id])
-
- if @invitation.attending.eql? true
- redirect_to root_path, notice: t("messages.already_rsvped")
-
- elsif has_remaining_seats?(@invitation)
- @invitation.update_attribute(:attending, true)
-
- redirect_to root_path, notice: t("messages.accepted_invitation",
- name: @invitation.member.name)
- else
- redirect_to root_path, notice: t("messages.no_available_seats")
- end
end
- private
def has_remaining_seats? invitation
invitation.course.seats > invitation.course.attending_invitations.length
end
diff --git a/app/controllers/invitation_controller.rb b/app/controllers/invitation_controller.rb
index 54b4de4e7..24e42224f 100644
--- a/app/controllers/invitation_controller.rb
+++ b/app/controllers/invitation_controller.rb
@@ -1,22 +1,13 @@
class InvitationController < ApplicationController
+ include InvitationControllerConcerns
- def accept
- @invitation = SessionInvitation.find_by_token(params[:id])
-
- if @invitation.attending.eql? true
- redirect_to root_path, notice: t("messages.already_rsvped")
- elsif has_remaining_seats?(@invitation)
- @invitation.update_attribute(:attending, true)
+ private
- redirect_to root_path, notice: t("messages.accepted_invitation",
- name: @invitation.member.name)
- else
- redirect_to root_path, notice: t("messages.no_available_seats")
- end
+ def set_invitation
+ @invitation = SessionInvitation.find_by_token(params[:id])
end
- private
def has_remaining_seats? invitation
invitation.sessions.seats > invitation.sessions.attending_invitations.length
end
diff --git a/app/mailers/session_invitation_mailer.rb b/app/mailers/session_invitation_mailer.rb
index 74af0983a..410c3cf7b 100644
--- a/app/mailers/session_invitation_mailer.rb
+++ b/app/mailers/session_invitation_mailer.rb
@@ -15,6 +15,20 @@ def invite_student sessions, member, invitation
end
end
+ def remind_student session, member, invitation
+ @session = session
+ @member = member
+ @invitation = invitation
+
+ load_attachments
+
+ subject = "Reminder for #{@session.title} by Codebar - #{l(@session.date_and_time, format: :email_title)}"
+
+ mail(mail_args(member, subject)) do |format|
+ format.html
+ end
+ end
+
private
def load_attachments
diff --git a/app/models/concerns/invitable.rb b/app/models/concerns/invitable.rb
new file mode 100644
index 000000000..abf2c547c
--- /dev/null
+++ b/app/models/concerns/invitable.rb
@@ -0,0 +1,15 @@
+module Invitable
+ extend ActiveSupport::Concern
+
+ included do
+ include InstanceMethods
+ end
+
+ module InstanceMethods
+
+ def attending_invitations
+ invitations.accepted
+ end
+
+ end
+end
diff --git a/app/models/course.rb b/app/models/course.rb
index 14d16b077..925985f5c 100644
--- a/app/models/course.rb
+++ b/app/models/course.rb
@@ -1,5 +1,7 @@
class Course < ActiveRecord::Base
- has_many :course_invitations
+ include Invitable
+
+ has_many :invitations, class_name: "CourseInvitation"
belongs_to :tutor, class_name: 'Member', foreign_key: 'tutor_id'
before_save :set_slug
@@ -9,10 +11,6 @@ class Course < ActiveRecord::Base
scope :upcoming, -> { where("date_and_time >= ?", DateTime.now).order(:date_and_time) }
scope :past, -> { where("date_and_time < ?", DateTime.now).order(:date_and_time) }
- def attending_invitations
- course_invitations.accepted
- end
-
def to_param
slug
end
diff --git a/app/models/invitation_manager.rb b/app/models/invitation_manager.rb
index 94381961b..1a5729a36 100644
--- a/app/models/invitation_manager.rb
+++ b/app/models/invitation_manager.rb
@@ -12,4 +12,10 @@ def self.send_course_emails course
end
end
+ def self.send_session_reminders session
+ session.attending_invitations.map do |invitation|
+ invitation.send_reminder
+ end
+ end
+
end
diff --git a/app/models/reminders.rb b/app/models/reminders.rb
new file mode 100644
index 000000000..bb892e936
--- /dev/null
+++ b/app/models/reminders.rb
@@ -0,0 +1,8 @@
+class Reminders < ActiveRecord::Base
+
+ scope :session, ->(session) { where(reminder_type: "session", reminder_id: session.id) }
+
+ def self.add_for_session session, count
+ Reminders.create reminder_type: "session", reminder_id: session.id, count: count
+ end
+end
diff --git a/app/models/session_invitation.rb b/app/models/session_invitation.rb
index 3c400224b..9a57e272d 100644
--- a/app/models/session_invitation.rb
+++ b/app/models/session_invitation.rb
@@ -7,6 +7,10 @@ class SessionInvitation < ActiveRecord::Base
validates :sessions, :member, presence: true
validates :member_id, uniqueness: { scope: [:sessions ] }
+ def send_reminder
+ SessionInvitationMailer.remind_student(self.sessions, self.member, self).deliver if attending
+ end
+
private
def email
diff --git a/app/models/sessions.rb b/app/models/sessions.rb
index 8f13492f8..2184c9998 100644
--- a/app/models/sessions.rb
+++ b/app/models/sessions.rb
@@ -1,12 +1,11 @@
class Sessions < ActiveRecord::Base
+ include Invitable
- has_many :session_invitations
+ has_many :invitations, class_name: "SessionInvitation"
has_many :sponsor_sessions
has_many :sponsors, through: :sponsor_sessions
- scope :upcoming, -> { where("date_and_time > ?", DateTime.now) }
+ scope :upcoming, -> { where("date_and_time >= ?", DateTime.now).order(:date_and_time) }
+ scope :next, -> { upcoming.first }
- def attending_invitations
- session_invitations.accepted
- end
end
diff --git a/app/views/session_invitation_mailer/remind_student.html.erb b/app/views/session_invitation_mailer/remind_student.html.erb
new file mode 100644
index 000000000..c6958e066
--- /dev/null
+++ b/app/views/session_invitation_mailer/remind_student.html.erb
@@ -0,0 +1,33 @@
+
Hi <%= @member.name %>
+
+<%= @session.title %> by Codebar is almost here!
+ If your are unable to attend, please change your attendance status.
+
+
+
+
<%= @session.title %> by Codebar
+
<%= @session.description %>
+
on <%= l(@session.date_and_time, format: :email)%>
+
+
<%= link_to "I can no longer attend", full_url_for(reject_invitation_url(@invitation)), style: "text-decoration:none; color: #663095; font-size: 25px; font-weight: 600; letter-spacing: 0.02em; margin: 10px; text-decoration: none; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);"%>
+
+
+
+
+
+
Location
+
Shutl
+
+ The Courtyard building
+ 11 Curtain Road, 2nd Floor
+ London, EC2A 3LT
+
+
view map
+
<%=image_tag(attachments["shutl.png"].url, width: "120px") %>
+
+
+
+
+
+See you on Wednesday!
+
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 560f3a4db..936f8e223 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -13,8 +13,10 @@ en:
date: "%d %B %Y"
messages:
- accepted_invitation: "Thanks for getting back to us %{name}. See you at the meeting!"
- already_rsvped: "You have alredy confirmed you attendance for this meeting!"
+ accepted_invitation: "Thanks for getting back to us %{name}. See you at the session!"
+ rejected_invitation: "We are so sad you can't make it, but thanks for letting us know %{name}."
+ already_rsvped: "You have alredy confirmed you attendance!"
+ not_attending_already: "You have alredy confirmed you can't make it!"
no_available_seats: "Unfortunately there are no more spaces left :(. If you would like to join our waiting list send us an email at meetings@codebar.io."
member_notifications: "You will be receiving notifications for: %{roles}"
no_roles: "You have not signed up for any notifications. If you change your mind, send us an email at meetins@codebar.io so we can change your settings"
diff --git a/config/routes.rb b/config/routes.rb
index 11789b393..fcdb760fa 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -14,6 +14,7 @@
resources :invitation, only: [] do
member do
get "accept"
+ get "reject"
end
end
@@ -21,6 +22,7 @@
resources :invitation, only: [] do
member do
get "accept"
+ get "reject"
end
end
end
diff --git a/db/migrate/20131104030726_create_reminders.rb b/db/migrate/20131104030726_create_reminders.rb
new file mode 100644
index 000000000..d155a8997
--- /dev/null
+++ b/db/migrate/20131104030726_create_reminders.rb
@@ -0,0 +1,12 @@
+class CreateReminders < ActiveRecord::Migration
+ def change
+ create_table :reminders do |t|
+ t.string :reminder_type
+ t.string :reminder_id
+ t.datetime :date_and_time
+ t.integer :count
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2434e9270..2bed75bc2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20131103212835) do
+ActiveRecord::Schema.define(version: 20131104030726) do
create_table "addresses", force: true do |t|
t.string "flat"
@@ -68,6 +68,15 @@
add_index "members_roles", ["member_id", "role_id"], name: "index_members_roles_on_member_id_and_role_id"
add_index "members_roles", ["member_id"], name: "index_members_roles_on_member_id"
+ create_table "reminders", force: true do |t|
+ t.string "reminder_type"
+ t.string "reminder_id"
+ t.datetime "date_and_time"
+ t.integer "count"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
create_table "roles", force: true do |t|
t.string "name"
t.string "description"
diff --git a/lib/tasks/reminders.rake b/lib/tasks/reminders.rake
new file mode 100644
index 000000000..4ec497c50
--- /dev/null
+++ b/lib/tasks/reminders.rake
@@ -0,0 +1,13 @@
+namespace :reminders do
+ desc "Send out reminders"
+
+ task sessions: :environment do
+ Sessions.upcoming.first.tap do |session|
+ unless Reminders.session(session).exists?
+ invitations = InvitationManager.send_session_reminders(session)
+
+ Reminders.add_for_session session, invitations.length
+ end
+ end
+ end
+end
diff --git a/spec/fabricators/reminder_fabricator.rb b/spec/fabricators/reminder_fabricator.rb
new file mode 100644
index 000000000..775ef5a7d
--- /dev/null
+++ b/spec/fabricators/reminder_fabricator.rb
@@ -0,0 +1,7 @@
+Fabricator(:reminders) do
+ count 10
+end
+
+Fabricator(:session_reminder, from: :reminders) do
+ reminder_type "session"
+end
diff --git a/spec/fabricators/session_invitation_fabricator.rb b/spec/fabricators/session_invitation_fabricator.rb
index 8da35aaaa..a205c06e7 100644
--- a/spec/fabricators/session_invitation_fabricator.rb
+++ b/spec/fabricators/session_invitation_fabricator.rb
@@ -5,3 +5,7 @@
note "I'd love to attend"
sessions
end
+
+Fabricator(:attending_session_invitation, from: :session_invitation) do
+ attending true
+end
diff --git a/spec/features/accepting_invitation_spec.rb b/spec/features/accepting_invitation_spec.rb
index 71ac39beb..d9199611e 100644
--- a/spec/features/accepting_invitation_spec.rb
+++ b/spec/features/accepting_invitation_spec.rb
@@ -1,10 +1,11 @@
require 'spec_helper'
-feature 'a member can accept an invitation' do
+feature 'a member can' do
- context "session" do
+ context "#session" do
let(:invitation) { Fabricate(:session_invitation) }
- let(:invitation_path) { accept_invitation_path(invitation) }
+ let(:accepting_invitation_path) { accept_invitation_path(invitation) }
+ let(:rejecting_invitation_path) { reject_invitation_path(invitation) }
let(:set_no_available_slots) { invitation.sessions.update_attribute(:seats, 0) }
@@ -12,9 +13,10 @@
end
- context "course" do
+ context "#course" do
let(:invitation) { Fabricate(:course_invitation) }
- let(:invitation_path) { accept_course_invitation_path(invitation) }
+ let(:accepting_invitation_path) { accept_course_invitation_path(invitation) }
+ let(:rejecting_invitation_path) { reject_course_invitation_path(invitation) }
let(:set_no_available_slots) { invitation.course.update_attribute(:seats, 0) }
diff --git a/spec/mailers/session_invitation_mailer_spec.rb b/spec/mailers/session_invitation_mailer_spec.rb
index 2a0799b85..bb4b9d81b 100644
--- a/spec/mailers/session_invitation_mailer_spec.rb
+++ b/spec/mailers/session_invitation_mailer_spec.rb
@@ -3,14 +3,22 @@
describe SessionInvitationMailer do
let(:email) { ActionMailer::Base.deliveries.last }
+ let(:session) { Fabricate(:sessions, title: "HTML & CSS") }
+ let(:member) { Fabricate(:member) }
+ let(:invitation) { Fabricate(:session_invitation, sessions: session, member: member) }
it "#invite_student" do
- member = Fabricate(:member)
- sessions = Fabricate(:sessions, title: "HTML & CSS", date_and_time: DateTime.new(2013,10,30,18,30))
invitation_token = "token"
- email_subject = "HTML & CSS by Codebar - Wednesday, 30 Oct at 18:30"
- SessionInvitationMailer.invite_student(sessions, member, invitation_token).deliver
+ email_subject = "#{session.title} by Codebar - #{I18n.l(session.date_and_time, format: :email_title)}"
+ SessionInvitationMailer.invite_student(session, member, invitation).deliver
+
+ expect(email.subject).to eq(email_subject)
+ end
+
+ it "#remind_student" do
+ email_subject = "Reminder for #{session.title} by Codebar - #{I18n.l(session.date_and_time, format: :email_title)}"
+ SessionInvitationMailer.remind_student(session, member, invitation).deliver
expect(email.subject).to eq(email_subject)
end
diff --git a/spec/models/invitation_manager_spec.rb b/spec/models/invitation_manager_spec.rb
index b022f196f..fba0bca97 100644
--- a/spec/models/invitation_manager_spec.rb
+++ b/spec/models/invitation_manager_spec.rb
@@ -3,9 +3,9 @@
describe InvitationManager do
let(:students) { 3.times.map { Fabricate(:student) } }
+ let(:session) { Fabricate(:sessions) }
it "#send_session_emails" do
- session = Fabricate(:sessions)
Member.should_receive(:students).and_return(students)
students.each do |student|
@@ -25,4 +25,12 @@
InvitationManager.send_course_emails course
end
+
+ it "#send_session_reminders" do
+ Fabricate(:attending_session_invitation, sessions: session)
+
+ SessionInvitation.any_instance.should_receive(:send_reminder)
+
+ InvitationManager.send_session_reminders session
+ end
end
diff --git a/spec/models/reminders_spec.rb b/spec/models/reminders_spec.rb
new file mode 100644
index 000000000..e660de659
--- /dev/null
+++ b/spec/models/reminders_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Reminders do
+ let(:session) { Fabricate(:sessions) }
+
+ context "#scopes" do
+
+ it "session" do
+ Fabricate(:session_reminder, reminder_id: session.id)
+
+ Reminders.session(session).should_not be nil
+ end
+ end
+
+ it "adds a reminder for the given session" do
+ Reminders.add_for_session(session, 2)
+
+ Reminders.session(session).exists?.should eq session.id
+ end
+end
diff --git a/spec/models/session_invitation_spec.rb b/spec/models/session_invitation_spec.rb
index a05b67311..41e71617d 100644
--- a/spec/models/session_invitation_spec.rb
+++ b/spec/models/session_invitation_spec.rb
@@ -6,4 +6,13 @@
it_behaves_like InvitationConcerns
+ it "#send_reminder" do
+ invitation.update_attribute(:attending, true)
+ mailer = mock(SessionInvitationMailer, deliver: nil)
+
+ SessionInvitationMailer.should_receive(:remind_student).
+ with(invitation.sessions, invitation.member, invitation).and_return(mailer)
+
+ invitation.send_reminder
+ end
end
diff --git a/spec/models/sessions_spec.rb b/spec/models/sessions_spec.rb
index f3ee1ff05..ed3b0e117 100644
--- a/spec/models/sessions_spec.rb
+++ b/spec/models/sessions_spec.rb
@@ -11,12 +11,20 @@
it { should respond_to(:sponsor_sessions)}
context "#scopes" do
- it "#upcoming" do
+ let(:set_upcoming) { 2.times.map { |n| Sessions.create date_and_time: DateTime.now+(n+1).week } }
+
+ before do
Sessions.create date_and_time: DateTime.now-1.week
- 2.times { |n| Sessions.create date_and_time: DateTime.now+(n+1).week }
+ set_upcoming
+ end
+ it "#upcoming" do
Sessions.upcoming.length.should eq 2
end
+
+ it "#next" do
+ Sessions.next.should eq set_upcoming.first
+ end
end
it "#attending" do
diff --git a/spec/support/shared_examples/behaves_like_an_invitation_route.rb b/spec/support/shared_examples/behaves_like_an_invitation_route.rb
index d43d65e42..b605d0e63 100644
--- a/spec/support/shared_examples/behaves_like_an_invitation_route.rb
+++ b/spec/support/shared_examples/behaves_like_an_invitation_route.rb
@@ -1,24 +1,45 @@
shared_examples "invitation route" do
- scenario 'when there are available spots' do
- visit invitation_path
+ context "accept an invitation" do
+ scenario 'when there are available spots' do
+ visit accepting_invitation_path
- current_url.should eq root_url
- expect(page).to have_content("Thanks for getting back to us #{invitation.member.name}.")
- end
+ current_url.should eq root_url
+ expect(page).to have_content("Thanks for getting back to us #{invitation.member.name}.")
+ end
+
+ scenario 'when they have already confirmed their attendance' do
+ invitation.update_attribute(:attending, true)
+ visit accepting_invitation_path
+
+ current_url.should eq root_url
+ expect(page).to have_content("You have alredy confirmed you attendance!")
+ end
- scenario 'when they have already confirmed their attendance' do
- invitation.update_attribute(:attending, true)
- visit invitation_path
+ scenario 'when there are available spots' do
+ set_no_available_slots
+ visit accepting_invitation_path
- current_url.should eq root_url
- expect(page).to have_content("You have alredy confirmed you attendance for this meeting!")
+ current_url.should eq root_url
+ expect(page).to have_content("Unfortunately there are no more spaces left :(.")
+ end
end
- scenario 'when there are available spots' do
- set_no_available_slots
- visit invitation_path
+ context "reject an invitation" do
+ scenario 'when they are succesful' do
+ invitation.update_attribute(:attending, true)
+ visit rejecting_invitation_path
+
+ current_url.should eq root_url
+ expect(page).to have_content(I18n.t("messages.rejected_invitation", name: invitation.member.name))
+ end
+
+ scenario 'when already confirmed' do
+ invitation.update_attribute(:attending, false)
+ visit rejecting_invitation_path
+
+ current_url.should eq root_url
+ expect(page).to have_content(I18n.t("messages.not_attending_already"))
+ end
- current_url.should eq root_url
- expect(page).to have_content("Unfortunately there are no more spaces left :(.")
end
end