From ed56a5e605ee537b17b38d27c485ecf88e2f6f55 Mon Sep 17 00:00:00 2001 From: Oliver Azevedo Barnes Date: Fri, 5 Feb 2016 14:49:40 -0200 Subject: [PATCH] Scheduling a call. Closes #24 --- .gitignore | 2 + .rubocop.yml | 10 +- Gemfile | 4 +- Gemfile.lock | 10 +- Guardfile | 26 +- .../v2/event_invitations_controller.rb | 39 ++ app/controllers/v2/reservations_controller.rb | 48 +++ app/helpers/application_helper.rb | 10 + app/mailers/application_mailer.rb | 4 + app/mailers/event_invitation_mailer.rb | 10 + app/mailers/reservation_notifier.rb | 10 + app/models/comment.rb | 1 - app/models/v2/event.rb | 6 + app/models/v2/event_invitation.rb | 32 ++ app/models/v2/reservation.rb | 9 + app/models/v2/time_slot.rb | 16 + app/models/v2/time_window.rb | 42 ++ .../event_invitation_mailer/invite.html.erb | 8 + .../event_invitation_mailer/invite.text.erb | 7 + app/views/layouts/mailer.html.erb | 5 + app/views/layouts/mailer.text.erb | 1 + .../reservation_notifier/notify.html.erb | 1 + .../reservation_notifier/notify.text.erb | 1 + app/views/v2/event_invitations/_form.html.erb | 48 +++ app/views/v2/event_invitations/new.html.erb | 4 + app/views/v2/reservations/_form.html.erb | 13 + app/views/v2/reservations/new.html.erb | 4 + config/application.rb | 2 +- config/routes.rb | 5 + db/migrate/20160208135550_add_v2_event.rb | 8 + .../20160210142609_create_v2_time_slots.rb | 9 + .../20160214152610_create_v2_reservations.rb | 8 + db/schema.rb | 405 +++++++++--------- spec/factories/events.rb | 12 + spec/factories/people.rb | 2 +- spec/factories/time_slots.rb | 8 + spec/factories/users.rb | 10 + .../invite_person_to_phone_call_spec.rb | 64 +++ ...n_responds_to_interview_invitation_spec.rb | 47 ++ spec/mailers/event_invitation_mailer_spec.rb | 4 + .../event_invitation_mailer_preview.rb | 4 + spec/models/v2/event_invitation_spec.rb | 41 ++ spec/models/v2/event_spec.rb | 6 + spec/models/v2/reservation_spec.rb | 6 + spec/models/v2/time_slot_spec.rb | 6 + spec/models/v2/time_window_spec.rb | 36 ++ spec/rails_helper.rb | 29 +- 47 files changed, 870 insertions(+), 213 deletions(-) create mode 100644 app/controllers/v2/event_invitations_controller.rb create mode 100644 app/controllers/v2/reservations_controller.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/mailers/application_mailer.rb create mode 100644 app/mailers/event_invitation_mailer.rb create mode 100644 app/mailers/reservation_notifier.rb create mode 100644 app/models/v2/event.rb create mode 100644 app/models/v2/event_invitation.rb create mode 100644 app/models/v2/reservation.rb create mode 100644 app/models/v2/time_slot.rb create mode 100644 app/models/v2/time_window.rb create mode 100644 app/views/event_invitation_mailer/invite.html.erb create mode 100644 app/views/event_invitation_mailer/invite.text.erb create mode 100644 app/views/layouts/mailer.html.erb create mode 100644 app/views/layouts/mailer.text.erb create mode 100644 app/views/reservation_notifier/notify.html.erb create mode 100644 app/views/reservation_notifier/notify.text.erb create mode 100644 app/views/v2/event_invitations/_form.html.erb create mode 100644 app/views/v2/event_invitations/new.html.erb create mode 100644 app/views/v2/reservations/_form.html.erb create mode 100644 app/views/v2/reservations/new.html.erb create mode 100644 db/migrate/20160208135550_add_v2_event.rb create mode 100644 db/migrate/20160210142609_create_v2_time_slots.rb create mode 100644 db/migrate/20160214152610_create_v2_reservations.rb create mode 100644 spec/factories/events.rb create mode 100644 spec/factories/time_slots.rb create mode 100644 spec/factories/users.rb create mode 100644 spec/features/invite_person_to_phone_call_spec.rb create mode 100644 spec/features/person_responds_to_interview_invitation_spec.rb create mode 100644 spec/mailers/event_invitation_mailer_spec.rb create mode 100644 spec/mailers/previews/event_invitation_mailer_preview.rb create mode 100644 spec/models/v2/event_invitation_spec.rb create mode 100644 spec/models/v2/event_spec.rb create mode 100644 spec/models/v2/reservation_spec.rb create mode 100644 spec/models/v2/time_slot_spec.rb create mode 100644 spec/models/v2/time_window_spec.rb diff --git a/.gitignore b/.gitignore index 4f6565a58..66ee0c067 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ # vagrant instance folder .vagrant + +Procfile diff --git a/.rubocop.yml b/.rubocop.yml index 177cf1034..9d5ee2345 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -28,10 +28,10 @@ Style/DotPosition: EnforcedStyle: trailing Style/EmptyLinesAroundClassBody: - EnforcedStyle: empty_lines + Enabled: false Style/EmptyLinesAroundModuleBody: - EnforcedStyle: empty_lines + Enabled: false Style/IndentationConsistency: EnforcedStyle: rails @@ -67,6 +67,12 @@ Style/BlockDelimiters: Style/ClassAndModuleChildren: Enabled: false +Style/ExtraSpacing: + Enabled: false + +Style/SpaceAroundOperators: + Enabled: false + Metrics/AbcSize: Max: 25 diff --git a/Gemfile b/Gemfile index 2251dea34..bbdaf0710 100644 --- a/Gemfile +++ b/Gemfile @@ -132,7 +132,9 @@ group :development, :test do gem 'guard-rubocop' gem 'guard-bundler', require: false gem 'capybara' + gem 'capybara-email' gem 'pry' gem 'factory_girl_rails' - gem 'shoulda-matchers', require: false + gem 'shoulda-matchers', '~> 3.1.1', require: false + gem 'database_cleaner' end diff --git a/Gemfile.lock b/Gemfile.lock index a5cc9bca7..fe6008c85 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,6 +68,9 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) + capybara-email (2.4.0) + capybara (~> 2.4) + mail coderay (1.1.0) coffee-rails (4.1.1) coffee-script (>= 2.2.0) @@ -78,6 +81,7 @@ GEM coffee-script-source (1.10.0) concurrent-ruby (1.0.0) daemons (1.2.3) + database_cleaner (1.5.1) debug_inspector (0.0.2) delayed_job (4.1.1) activesupport (>= 3.0, < 5.0) @@ -316,7 +320,7 @@ GEM sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) shellany (0.0.1) - shoulda-matchers (3.1.0) + shoulda-matchers (3.1.1) activesupport (>= 4.0.0) slop (3.6.0) sprockets (3.5.2) @@ -399,8 +403,10 @@ DEPENDENCIES bullet capistrano! capybara + capybara-email coffee-rails daemons + database_cleaner delayed_job_active_record devise factory_girl_rails @@ -434,7 +440,7 @@ DEPENDENCIES ruby-prof rvm-capistrano sass-rails - shoulda-matchers + shoulda-matchers (~> 3.1.1) sqlite3 stackprof tire diff --git a/Guardfile b/Guardfile index 3f0ed6335..d31b4fd4b 100644 --- a/Guardfile +++ b/Guardfile @@ -1,6 +1,6 @@ # This group allows to skip running RuboCop when RSpec failed. group :red_green_refactor, halt_on_fail: true do - guard :rspec, cmd: "bundle exec rspec" do + guard :rspec, cmd: "bundle exec rspec --fail-fast" do require "guard/rspec/dsl" dsl = Guard::RSpec::Dsl.new(self) @@ -41,9 +41,31 @@ group :red_green_refactor, halt_on_fail: true do watch('spec/features/people_registration_spec.rb') watch('app/controllers/public/people_controller.rb') { 'spec/features/people_registration_spec.rb' } watch('app/views/public/people/new.html.erb') { 'spec/features/people_registration_spec.rb' } + watch('app/views/public/people/_form.html.erb') { 'spec/features/people_registration_spec.rb' } + + watch('app/models/v2/event.rb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/models/v2/event_invitation.rb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/models/v2/time_window.rb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/models/v2/time_slot.rb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/mailers/event_invitation_mailer.rb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/controllers/v2/event_invitations_controller.rb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/views/v2/event_invitations/new.html.erb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/views/v2/event_invitations/_form.html.erb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/views/event_invitation_mailer/invite.html.erb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + watch('app/views/event_invitation_mailer/invite.text.erb') { 'spec/features/invite_person_to_phone_call_spec.rb' } + + watch('spec/features/person_responds_to_interview_invitation_spec.rb') + watch('app/views/event_invitation_mailer/invite.html.erb') { 'spec/features/person_responds_to_interview_invitation_spec.rb' } + watch('app/views/event_invitation_mailer/invite.text.erb') { 'spec/features/person_responds_to_interview_invitation_spec.rb' } + + watch('app/mailers/event_invitation_mailer.rb') { 'spec/features/person_responds_to_interview_invitation_spec.rb' } + watch('app/models/v2/reservation.rb') { 'spec/features/person_responds_to_interview_invitation_spec.rb' } + watch('app/controllers/v2/reservations_controller.rb') { 'spec/features/person_responds_to_interview_invitation_spec.rb' } + watch('app/views/v2/reservations/new.html.erb') { 'spec/features/person_responds_to_interview_invitation_spec.rb' } + watch('app/views/v2/reservations/_form.html.erb') { 'spec/features/person_responds_to_interview_invitation_spec.rb' } end - guard :minitest do + guard :minitest, test_folders: ['test'] do watch(%r{^test/(.*)\/?test_(.*)\.rb$}) watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" } watch(%r{^test/test_helper\.rb$}) { 'test' } diff --git a/app/controllers/v2/event_invitations_controller.rb b/app/controllers/v2/event_invitations_controller.rb new file mode 100644 index 000000000..325040eab --- /dev/null +++ b/app/controllers/v2/event_invitations_controller.rb @@ -0,0 +1,39 @@ +class V2::EventInvitationsController < ApplicationController + def new + @event_invitation = V2::EventInvitation.new + end + + def create + @event_invitation = V2::EventInvitation.new(event_invitation_params) + + if @event_invitation.save + send_notifications(@event_invitation) + flash[:notice] = 'Person was successfully invited.' + else + flash[:error] = 'There were problems with some of the fields.' + end + + render :new + end + + private + + def send_notifications(event_invitation) + EventInvitationMailer.invite( + email_address: event_invitation.email_address, + event: event_invitation.event + ).deliver_later + end + + def event_invitation_params + params.require(:v2_event_invitation). + permit( + :email_address, + :description, + :slot_length, + :date, + :start_time, + :end_time + ) + end +end diff --git a/app/controllers/v2/reservations_controller.rb b/app/controllers/v2/reservations_controller.rb new file mode 100644 index 000000000..24ab995ff --- /dev/null +++ b/app/controllers/v2/reservations_controller.rb @@ -0,0 +1,48 @@ +class V2::ReservationsController < ApplicationController + skip_before_action :authenticate_user! + + def new + event = V2::Event.find(event_params[:event_id]) + @time_slots = event.time_slots + @person = Person.find_by(email_address: person_params[:email_address]) + @reservation = V2::Reservation.new(time_slot: V2::TimeSlot.new) + end + + def create + @reservation = V2::Reservation.new(reservation_params) + + if @reservation.save + flash[:notice] = "An interview has been booked for #{@reservation.time_slot.to_weekday_and_time}" + + send_notifications(@reservation) + else + flash[:error] = "No time slot was selected, couldn't create the reservation" + end + + @time_slots = [] + @person = @reservation.person + + render :new + end + + private + + def send_notifications(reservation) + ReservationNotifier.notify( + email_address: reservation.person.email_address, + reservation: reservation + ).deliver_later + end + + def event_params + params.permit(:event_id) + end + + def reservation_params + params.require(:v2_reservation).permit(:person_id, :time_slot_id) + end + + def person_params + params.permit(:email_address, :person_id) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 000000000..263100b47 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,10 @@ +module ApplicationHelper + def simple_time_select_options + minutes = %w( 00 15 30 45 ) + hours = (0..23).to_a.map { |h| format('%.2d', h) } + options = hours.map do |h| + minutes.map { |m| "#{h}:#{m}" } + end.flatten + options_for_select(options) + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 000000000..a479497df --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'admin@what.host.should.we.have.here.com' + layout 'mailer' +end diff --git a/app/mailers/event_invitation_mailer.rb b/app/mailers/event_invitation_mailer.rb new file mode 100644 index 000000000..06ac6aac5 --- /dev/null +++ b/app/mailers/event_invitation_mailer.rb @@ -0,0 +1,10 @@ +class EventInvitationMailer < ApplicationMailer + def invite(email_address:, event:) + admin_email = 'admin@what.host.should.we.have.here.com' + @email_address = email_address + @event = event + mail(to: email_address, + bcc: admin_email, + subject: 'Phone call interview') + end +end diff --git a/app/mailers/reservation_notifier.rb b/app/mailers/reservation_notifier.rb new file mode 100644 index 000000000..c1da9e1ce --- /dev/null +++ b/app/mailers/reservation_notifier.rb @@ -0,0 +1,10 @@ +class ReservationNotifier < ApplicationMailer + def notify(email_address:, reservation:) + admin_email = 'admin@what.host.should.we.have.here.com' + @email_address = email_address + @reservation = reservation + mail(to: email_address, + bcc: admin_email, + subject: 'Interview scheduled') + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index 338adb55a..2c10df8cd 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -16,5 +16,4 @@ class Comment < ActiveRecord::Base validates_presence_of :content belongs_to :commentable, polymorphic: true, touch: true - end diff --git a/app/models/v2/event.rb b/app/models/v2/event.rb new file mode 100644 index 000000000..a21b34c24 --- /dev/null +++ b/app/models/v2/event.rb @@ -0,0 +1,6 @@ +class V2::Event < ActiveRecord::Base + has_many :time_slots, class_name: '::V2::TimeSlot' + + validates :description, presence: true + validates :time_slots, presence: true +end diff --git a/app/models/v2/event_invitation.rb b/app/models/v2/event_invitation.rb new file mode 100644 index 000000000..f99f17d39 --- /dev/null +++ b/app/models/v2/event_invitation.rb @@ -0,0 +1,32 @@ +class V2::EventInvitation + include ActiveModel::Model + + attr_accessor :email_address, :description, :slot_length, :date, :start_time, :end_time + attr_reader :event + + validates :email_address, :description, :slot_length, :date, :start_time, :end_time, presence: true + + def save + if valid? + @event = V2::Event.new( + description: description, + time_slots: time_slots + ) + + @event.save! + else + false + end + end + + private + + def time_slots + V2::TimeWindow.new( + slot_length: slot_length, + date: date, + start_time: start_time, + end_time: end_time + ).slots + end +end diff --git a/app/models/v2/reservation.rb b/app/models/v2/reservation.rb new file mode 100644 index 000000000..1875956cb --- /dev/null +++ b/app/models/v2/reservation.rb @@ -0,0 +1,9 @@ +class V2::Reservation < ActiveRecord::Base + self.table_name = 'v2_reservations' + + belongs_to :time_slot, class_name: '::V2::TimeSlot' + belongs_to :person + + validates :person, presence: true + validates :time_slot, presence: true +end diff --git a/app/models/v2/time_slot.rb b/app/models/v2/time_slot.rb new file mode 100644 index 000000000..e1b927207 --- /dev/null +++ b/app/models/v2/time_slot.rb @@ -0,0 +1,16 @@ +class V2::TimeSlot < ActiveRecord::Base + self.table_name = 'v2_time_slots' + + belongs_to :event, class_name: '::V2::Event' + + validates :start_time, presence: true + validates :end_time, presence: true + + def to_time_and_weekday + "#{start_time.strftime('%H:%M')} - #{end_time.strftime('%H:%M')} #{start_time.strftime('%A %d')}" + end + + def to_weekday_and_time + "#{start_time.strftime('%A %d')} #{start_time.strftime('%H:%M')} - #{end_time.strftime('%H:%M')}" + end +end diff --git a/app/models/v2/time_window.rb b/app/models/v2/time_window.rb new file mode 100644 index 000000000..5cdfbd1a7 --- /dev/null +++ b/app/models/v2/time_window.rb @@ -0,0 +1,42 @@ +class V2::TimeWindow + + def initialize(date:, start_time:, end_time:, slot_length:) + @date = date + @start_time = start_time + @end_time = end_time + @slot_length = slot_length + @slots = [] + end + + def slots + slot_start = start_time + slot_end = slot_start + slot_length + + while slot_end <= end_time + @slots << ::V2::TimeSlot.new(start_time: slot_start, end_time: slot_end) + + slot_start = slot_end + slot_end += slot_length + end + + @slots + end + + private + + def date + Date.strptime(@date, '%m/%d/%Y') + end + + def start_time + Time.zone.parse("#{date} #{@start_time}") + end + + def end_time + Time.zone.parse("#{date} #{@end_time}") + end + + def slot_length + @slot_length.delete(' mins').to_i.minutes + end +end diff --git a/app/views/event_invitation_mailer/invite.html.erb b/app/views/event_invitation_mailer/invite.html.erb new file mode 100644 index 000000000..404f8e085 --- /dev/null +++ b/app/views/event_invitation_mailer/invite.html.erb @@ -0,0 +1,8 @@ +

Hello, you've been invited to a phone interview

+ +

<%= link_to 'Please click to setup a time for your interview', + new_v2_reservation_url( + email_address: @email_address, + event_id: @event.id + ) %>

+ diff --git a/app/views/event_invitation_mailer/invite.text.erb b/app/views/event_invitation_mailer/invite.text.erb new file mode 100644 index 000000000..1abee6281 --- /dev/null +++ b/app/views/event_invitation_mailer/invite.text.erb @@ -0,0 +1,7 @@ +Hello, you've been invited to a phone interview + +<%= link_to 'Please click to setup a time for your interview', + new_v2_reservation_url( + email_address: @email_address, + event_id: @event.id + ) %> diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 000000000..991cf0ffa --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,5 @@ + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 000000000..37f0bddbd --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/reservation_notifier/notify.html.erb b/app/views/reservation_notifier/notify.html.erb new file mode 100644 index 000000000..a427697c7 --- /dev/null +++ b/app/views/reservation_notifier/notify.html.erb @@ -0,0 +1 @@ +

An interview has been booked for <%= @reservation.time_slot.to_weekday_and_time %>

diff --git a/app/views/reservation_notifier/notify.text.erb b/app/views/reservation_notifier/notify.text.erb new file mode 100644 index 000000000..02ac3b815 --- /dev/null +++ b/app/views/reservation_notifier/notify.text.erb @@ -0,0 +1 @@ +An interview has been booked for <%= @reservation.time_slot.to_weekday_and_time %> \ No newline at end of file diff --git a/app/views/v2/event_invitations/_form.html.erb b/app/views/v2/event_invitations/_form.html.erb new file mode 100644 index 000000000..8e836cdde --- /dev/null +++ b/app/views/v2/event_invitations/_form.html.erb @@ -0,0 +1,48 @@ +<%= form_for @event_invitation, url: v2_event_invitations_path, html: { class: 'form-horizontal' } do |f| %> +
+ <%= f.label :email_address, "Person's email address", :class => 'control-label' %> +
+ <%= f.text_field :email_address, :class => 'text_field' %> +
+
+
+ <%= f.label :description, 'Event description', :class => 'control-label' %> +
+ <%= f.text_area :description, :class => 'text_area' %> +
+
+
+ <%= f.label :slot_length, 'Call length', :class => 'control-label' %> +
+ <%= f.select :slot_length, options_for_select([15,30,45,60].map {|o| "#{o.to_s} mins"}), :class => 'text_field' %> +
+
+ +

Time window

+
+ <%= f.label :date, :class => 'control-label' %> +
+ <%= f.text_field :date, :class => 'text_field' %> +
+
+ +
+ <%= f.label :start_time, :class => 'control-label' %> +
+ <%= f.select :start_time, simple_time_select_options, :class => 'text_field' %> +
+
+ +
+ <%= f.label :end_time, :class => 'control-label' %> +
+ <%= f.select :end_time, simple_time_select_options, :class => 'text_field' %> +
+
+ +
+ <%= f.submit 'Send invitation', :class => 'btn btn-primary' %> + <%= link_to t('.cancel', :default => t("helpers.links.cancel")), + people_path, :class => 'btn' %> +
+<% end %> diff --git a/app/views/v2/event_invitations/new.html.erb b/app/views/v2/event_invitations/new.html.erb new file mode 100644 index 000000000..57e1bdf6c --- /dev/null +++ b/app/views/v2/event_invitations/new.html.erb @@ -0,0 +1,4 @@ +

Invite a person to a phone call

+ +<%= render 'form' %> + diff --git a/app/views/v2/reservations/_form.html.erb b/app/views/v2/reservations/_form.html.erb new file mode 100644 index 000000000..d1e712f60 --- /dev/null +++ b/app/views/v2/reservations/_form.html.erb @@ -0,0 +1,13 @@ +<%= form_for @reservation, url: v2_reservations_path, html: { class: 'form-horizontal' } do |form| %> + <%= form.hidden_field :person_id, value: @person.id.to_s %> + + <% @time_slots.each do |time_slot| %> +
+ <%= form.label :time_slot_id, time_slot.to_time_and_weekday, :class => 'control-label' %> + <%= form.radio_button :time_slot_id, time_slot.id, :class => 'radio_button' %> +
+ <% end %> + + <%= form.submit "Confirm reservation" %> + +<% end %> diff --git a/app/views/v2/reservations/new.html.erb b/app/views/v2/reservations/new.html.erb new file mode 100644 index 000000000..dfc985b36 --- /dev/null +++ b/app/views/v2/reservations/new.html.erb @@ -0,0 +1,4 @@ +

Please reserve a time for the call

+ +<%= render 'form' %> + diff --git a/config/application.rb b/config/application.rb index 21991850f..9c0812e1d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -20,7 +20,7 @@ class Application < Rails::Application # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de - config.autoload_paths += %W(#{config.root}/app/jobs) + config.autoload_paths += %W(#{config.root}/app/jobs #{config.root}/app/mailers) # Analytics Logan::Application.config.google_analytics_enabled = false diff --git a/config/routes.rb b/config/routes.rb index 8c6244df0..74887ab49 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,11 @@ resources :people, only: [:new, :create] end + namespace :v2 do + resources :event_invitations, only: [:new, :create] + resources :reservations, only: [:new, :create] + end + get 'registration', to: 'public/people#new' resources :twilio_wufoos diff --git a/db/migrate/20160208135550_add_v2_event.rb b/db/migrate/20160208135550_add_v2_event.rb new file mode 100644 index 000000000..97f2b6c49 --- /dev/null +++ b/db/migrate/20160208135550_add_v2_event.rb @@ -0,0 +1,8 @@ +class AddV2Event < ActiveRecord::Migration + def change + create_table :v2_events do |t| + t.integer :user_id + t.string :description + end + end +end diff --git a/db/migrate/20160210142609_create_v2_time_slots.rb b/db/migrate/20160210142609_create_v2_time_slots.rb new file mode 100644 index 000000000..e60719377 --- /dev/null +++ b/db/migrate/20160210142609_create_v2_time_slots.rb @@ -0,0 +1,9 @@ +class CreateV2TimeSlots < ActiveRecord::Migration + def change + create_table :v2_time_slots do |t| + t.integer :event_id + t.datetime :start_time + t.datetime :end_time + end + end +end diff --git a/db/migrate/20160214152610_create_v2_reservations.rb b/db/migrate/20160214152610_create_v2_reservations.rb new file mode 100644 index 000000000..3f1387286 --- /dev/null +++ b/db/migrate/20160214152610_create_v2_reservations.rb @@ -0,0 +1,8 @@ +class CreateV2Reservations < ActiveRecord::Migration + def change + create_table :v2_reservations do |t| + t.integer :time_slot_id + t.integer :person_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 41f6efa1c..8d2bebb94 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,199 +11,216 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151019004127) do - create_table 'applications', force: :cascade do |t| - t.string 'name', limit: 255 - t.text 'description', limit: 65535 - t.string 'url', limit: 255 - t.string 'source_url', limit: 255 - t.string 'creator_name', limit: 255 - t.datetime 'created_at' - t.datetime 'updated_at' - t.integer 'program_id', limit: 4 - t.integer 'created_by', limit: 4 - t.integer 'updated_by', limit: 4 - end - - create_table 'comments', force: :cascade do |t| - t.text 'content', limit: 65535 - t.integer 'user_id', limit: 4 - t.string 'commentable_type', limit: 255 - t.integer 'commentable_id', limit: 4 - t.datetime 'created_at' - t.datetime 'updated_at' - t.integer 'created_by', limit: 4 - end - - create_table 'delayed_jobs', force: :cascade do |t| - t.integer 'priority', limit: 4, default: 0, null: false - t.integer 'attempts', limit: 4, default: 0, null: false - t.text 'handler', limit: 65535, null: false - t.text 'last_error', limit: 65535 - t.datetime 'run_at' - t.datetime 'locked_at' - t.datetime 'failed_at' - t.string 'locked_by', limit: 255 - t.string 'queue', limit: 255 - t.datetime 'created_at' - t.datetime 'updated_at' - t.integer 'delayed_reference_id', limit: 4 - t.string 'delayed_reference_type', limit: 255 - end - - add_index 'delayed_jobs', ['delayed_reference_type'], name: 'delayed_jobs_delayed_reference_type', using: :btree - add_index 'delayed_jobs', %w(priority run_at), name: 'delayed_jobs_priority', using: :btree - add_index 'delayed_jobs', ['queue'], name: 'delayed_jobs_queue', using: :btree - - create_table 'events', force: :cascade do |t| - t.string 'name', limit: 255 - t.text 'description', limit: 65535 - t.datetime 'starts_at' - t.datetime 'ends_at' - t.text 'location', limit: 65535 - t.text 'address', limit: 65535 - t.integer 'capacity', limit: 4 - t.integer 'application_id', limit: 4 - t.datetime 'created_at' - t.datetime 'updated_at' - t.integer 'created_by', limit: 4 - t.integer 'updated_by', limit: 4 - end - - create_table 'mailchimp_exports', force: :cascade do |t| - t.string 'name', limit: 255 - t.text 'body', limit: 65535 - t.integer 'created_by', limit: 4 - t.datetime 'created_at' - t.datetime 'updated_at' - end - - create_table 'people', force: :cascade do |t| - t.string 'first_name', limit: 255 - t.string 'last_name', limit: 255 - t.string 'email_address', limit: 255 - t.string 'address_1', limit: 255 - t.string 'address_2', limit: 255 - t.string 'city', limit: 255 - t.string 'state', limit: 255 - t.string 'postal_code', limit: 255 - t.integer 'geography_id', limit: 4 - t.integer 'primary_device_id', limit: 4 - t.string 'primary_device_description', limit: 255 - t.integer 'secondary_device_id', limit: 4 - t.string 'secondary_device_description', limit: 255 - t.integer 'primary_connection_id', limit: 4 - t.string 'primary_connection_description', limit: 255 - t.string 'phone_number', limit: 255 - t.string 'participation_type', limit: 255 - t.datetime 'created_at' - t.datetime 'updated_at' - t.string 'signup_ip', limit: 255 - t.datetime 'signup_at' - t.string 'voted', limit: 255 - t.string 'called_311', limit: 255 - t.integer 'secondary_connection_id', limit: 4 - t.string 'secondary_connection_description', limit: 255 - t.string 'verified', limit: 255 - t.string 'preferred_contact_method', limit: 255 - end - - create_table 'programs', force: :cascade do |t| - t.string 'name', limit: 255 - t.text 'description', limit: 65535 - t.datetime 'created_at' - t.datetime 'updated_at' - t.integer 'created_by', limit: 4 - t.integer 'updated_by', limit: 4 - end - - create_table 'reservations', force: :cascade do |t| - t.integer 'person_id', limit: 4 - t.integer 'event_id', limit: 4 - t.datetime 'confirmed_at' - t.integer 'created_by', limit: 4 - t.datetime 'attended_at' - t.datetime 'created_at' - t.datetime 'updated_at' - t.integer 'updated_by', limit: 4 - end - - create_table 'submissions', force: :cascade do |t| - t.text 'raw_content', limit: 65535 - t.integer 'person_id', limit: 4 - t.string 'ip_addr', limit: 255 - t.string 'entry_id', limit: 255 - t.text 'form_structure', limit: 65535 - t.text 'field_structure', limit: 65535 - t.datetime 'created_at' - t.datetime 'updated_at' - end - - create_table 'taggings', force: :cascade do |t| - t.string 'taggable_type', limit: 255 - t.integer 'taggable_id', limit: 4 - t.integer 'created_by', limit: 4 - t.datetime 'created_at' - t.datetime 'updated_at' - t.integer 'tag_id', limit: 4 - end - - create_table 'tags', force: :cascade do |t| - t.string 'name', limit: 255 - t.integer 'created_by', limit: 4 - t.datetime 'created_at' - t.datetime 'updated_at' - end - - create_table 'twilio_messages', force: :cascade do |t| - t.string 'message_sid', limit: 255 - t.datetime 'date_created' - t.datetime 'date_updated' - t.datetime 'date_sent' - t.string 'account_sid', limit: 255 - t.string 'from', limit: 255 - t.string 'to', limit: 255 - t.string 'body', limit: 255 - t.string 'status', limit: 255 - t.string 'error_code', limit: 255 - t.string 'error_message', limit: 255 - t.string 'direction', limit: 255 - t.string 'from_city', limit: 255 - t.string 'from_state', limit: 255 - t.string 'from_zip', limit: 255 - t.string 'wufoo_formid', limit: 255 - t.integer 'conversation_count', limit: 4 - t.string 'signup_verify', limit: 255 - t.datetime 'created_at' - t.datetime 'updated_at' - end - - create_table 'twilio_wufoos', force: :cascade do |t| - t.string 'name', limit: 255 - t.string 'wufoo_formid', limit: 255 - t.string 'twilio_keyword', limit: 255 - t.datetime 'created_at' - t.datetime 'updated_at' - t.boolean 'status', default: false, null: false - t.string 'end_message', limit: 255 - t.string 'form_type', limit: 255 - end - - create_table 'users', force: :cascade do |t| - t.string 'email', limit: 255, default: '', null: false - t.string 'encrypted_password', limit: 255, default: '', null: false - t.string 'reset_password_token', limit: 255 - t.datetime 'reset_password_sent_at' - t.datetime 'remember_created_at' - t.integer 'sign_in_count', limit: 4, default: 0 - t.datetime 'current_sign_in_at' - t.datetime 'last_sign_in_at' - t.string 'current_sign_in_ip', limit: 255 - t.string 'last_sign_in_ip', limit: 255 - t.string 'password_salt', limit: 255 - t.string 'invitation_token', limit: 255 - t.datetime 'created_at' - t.datetime 'updated_at' - t.boolean 'approved', default: false, null: false +ActiveRecord::Schema.define(version: 20160214152610) do + + create_table "applications", force: :cascade do |t| + t.string "name", limit: 255 + t.text "description", limit: 65535 + t.string "url", limit: 255 + t.string "source_url", limit: 255 + t.string "creator_name", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "program_id", limit: 4 + t.integer "created_by", limit: 4 + t.integer "updated_by", limit: 4 + end + + create_table "comments", force: :cascade do |t| + t.text "content", limit: 65535 + t.integer "user_id", limit: 4 + t.string "commentable_type", limit: 255 + t.integer "commentable_id", limit: 4 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "created_by", limit: 4 + end + + create_table "delayed_jobs", force: :cascade do |t| + t.integer "priority", limit: 4, default: 0, null: false + t.integer "attempts", limit: 4, default: 0, null: false + t.text "handler", limit: 65535, null: false + t.text "last_error", limit: 65535 + t.datetime "run_at" + t.datetime "locked_at" + t.datetime "failed_at" + t.string "locked_by", limit: 255 + t.string "queue", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "delayed_reference_id", limit: 4 + t.string "delayed_reference_type", limit: 255 + end + + add_index "delayed_jobs", ["delayed_reference_type"], name: "delayed_jobs_delayed_reference_type", using: :btree + add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree + add_index "delayed_jobs", ["queue"], name: "delayed_jobs_queue", using: :btree + + create_table "events", force: :cascade do |t| + t.string "name", limit: 255 + t.text "description", limit: 65535 + t.datetime "starts_at" + t.datetime "ends_at" + t.text "location", limit: 65535 + t.text "address", limit: 65535 + t.integer "capacity", limit: 4 + t.integer "application_id", limit: 4 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "created_by", limit: 4 + t.integer "updated_by", limit: 4 + end + + create_table "mailchimp_exports", force: :cascade do |t| + t.string "name", limit: 255 + t.text "body", limit: 65535 + t.integer "created_by", limit: 4 + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "people", force: :cascade do |t| + t.string "first_name", limit: 255 + t.string "last_name", limit: 255 + t.string "email_address", limit: 255 + t.string "address_1", limit: 255 + t.string "address_2", limit: 255 + t.string "city", limit: 255 + t.string "state", limit: 255 + t.string "postal_code", limit: 255 + t.integer "geography_id", limit: 4 + t.integer "primary_device_id", limit: 4 + t.string "primary_device_description", limit: 255 + t.integer "secondary_device_id", limit: 4 + t.string "secondary_device_description", limit: 255 + t.integer "primary_connection_id", limit: 4 + t.string "primary_connection_description", limit: 255 + t.string "phone_number", limit: 255 + t.string "participation_type", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.string "signup_ip", limit: 255 + t.datetime "signup_at" + t.string "voted", limit: 255 + t.string "called_311", limit: 255 + t.integer "secondary_connection_id", limit: 4 + t.string "secondary_connection_description", limit: 255 + t.string "verified", limit: 255 + t.string "preferred_contact_method", limit: 255 + end + + create_table "programs", force: :cascade do |t| + t.string "name", limit: 255 + t.text "description", limit: 65535 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "created_by", limit: 4 + t.integer "updated_by", limit: 4 + end + + create_table "reservations", force: :cascade do |t| + t.integer "person_id", limit: 4 + t.integer "event_id", limit: 4 + t.datetime "confirmed_at" + t.integer "created_by", limit: 4 + t.datetime "attended_at" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "updated_by", limit: 4 + end + + create_table "submissions", force: :cascade do |t| + t.text "raw_content", limit: 65535 + t.integer "person_id", limit: 4 + t.string "ip_addr", limit: 255 + t.string "entry_id", limit: 255 + t.text "form_structure", limit: 65535 + t.text "field_structure", limit: 65535 + t.datetime "created_at" + t.datetime "updated_at" end + + create_table "taggings", force: :cascade do |t| + t.string "taggable_type", limit: 255 + t.integer "taggable_id", limit: 4 + t.integer "created_by", limit: 4 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "tag_id", limit: 4 + end + + create_table "tags", force: :cascade do |t| + t.string "name", limit: 255 + t.integer "created_by", limit: 4 + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "twilio_messages", force: :cascade do |t| + t.string "message_sid", limit: 255 + t.datetime "date_created" + t.datetime "date_updated" + t.datetime "date_sent" + t.string "account_sid", limit: 255 + t.string "from", limit: 255 + t.string "to", limit: 255 + t.string "body", limit: 255 + t.string "status", limit: 255 + t.string "error_code", limit: 255 + t.string "error_message", limit: 255 + t.string "direction", limit: 255 + t.string "from_city", limit: 255 + t.string "from_state", limit: 255 + t.string "from_zip", limit: 255 + t.string "wufoo_formid", limit: 255 + t.integer "conversation_count", limit: 4 + t.string "signup_verify", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "twilio_wufoos", force: :cascade do |t| + t.string "name", limit: 255 + t.string "wufoo_formid", limit: 255 + t.string "twilio_keyword", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "status", default: false, null: false + t.string "end_message", limit: 255 + t.string "form_type", limit: 255 + end + + create_table "users", force: :cascade do |t| + t.string "email", limit: 255, default: "", null: false + t.string "encrypted_password", limit: 255, default: "", null: false + t.string "reset_password_token", limit: 255 + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", limit: 4, default: 0 + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip", limit: 255 + t.string "last_sign_in_ip", limit: 255 + t.string "password_salt", limit: 255 + t.string "invitation_token", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "approved", default: false, null: false + end + + create_table "v2_events", force: :cascade do |t| + t.integer "user_id", limit: 4 + end + + create_table "v2_reservations", force: :cascade do |t| + t.integer "time_slot_id", limit: 4 + t.integer "person_id", limit: 4 + end + + create_table "v2_time_slots", force: :cascade do |t| + t.integer "event_id", limit: 4 + t.datetime "start_time" + t.datetime "end_time" + end + end diff --git a/spec/factories/events.rb b/spec/factories/events.rb new file mode 100644 index 000000000..ffe01d8c6 --- /dev/null +++ b/spec/factories/events.rb @@ -0,0 +1,12 @@ +require 'faker' + +FactoryGirl.define do + factory :event, class: V2::Event do + description 'Lorem ipsum for now' + + before(:create) do |event| + # create_list(:time_slot, 3, event: event) + 3.times { event.time_slots << FactoryGirl.create(:time_slot) } + end + end +end diff --git a/spec/factories/people.rb b/spec/factories/people.rb index d0e1025b5..24431be8a 100644 --- a/spec/factories/people.rb +++ b/spec/factories/people.rb @@ -7,7 +7,7 @@ factory :person do first_name Faker::Name.first_name last_name Faker::Name.last_name - email_address Faker::Internet.email + email_address { Faker::Internet.email } phone_number Faker::PhoneNumber.phone_number address_1 Faker::Address.street_address address_2 Faker::Address.secondary_address diff --git a/spec/factories/time_slots.rb b/spec/factories/time_slots.rb new file mode 100644 index 000000000..e8724c63c --- /dev/null +++ b/spec/factories/time_slots.rb @@ -0,0 +1,8 @@ +require 'faker' + +FactoryGirl.define do + factory :time_slot, class: V2::TimeSlot do + sequence(:start_time) { |i| Faker::Time.forward(i.day, :morning) } + end_time { start_time + 30.minutes } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 000000000..58d59c4a9 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,10 @@ +require 'faker' + +FactoryGirl.define do + factory :user do + email { Faker::Internet.email } + password 'password' + password_confirmation 'password' + approved true + end +end diff --git a/spec/features/invite_person_to_phone_call_spec.rb b/spec/features/invite_person_to_phone_call_spec.rb new file mode 100644 index 000000000..9779e0212 --- /dev/null +++ b/spec/features/invite_person_to_phone_call_spec.rb @@ -0,0 +1,64 @@ +require 'rails_helper' +require 'faker' +require 'capybara/email/rspec' + +feature 'Invite a person to a phone call' do + scenario 'with valid data' do + login_with_admin_user + + visit '/v2/event_invitations/new' + + research_subject_email = 'person@test.com.br' + admin_email = 'admin@what.host.should.we.have.here.com' + + fill_in "Person's email address", with: research_subject_email + + # TODO: allow to fill in multiple email addresses once basic invitation works + + fill_in 'Event description', with: "We're looking for mothers between the age of 16-26 for a phone interview" + + select '30 mins', from: 'Call length' + + fill_in 'Date', with: '02/02/2016' + select '12:00', from: 'Start time' + select '15:30', from: 'End time' + + # TODO: implement multiple time windows after invitation for single time window works + # + # click_link 'Add another time window' + # + # fill_in 'Date', with: '02/03/2016' + # select '12:00', from: 'Start time' + # select '14:30', from: 'End time' + + click_button 'Send invitation' + + expect(page).to have_text 'Person was successfully invited.' + + [research_subject_email, admin_email].each do |email_address| + open_email(email_address) + + # TODO: substitute placeholder text + expect(current_email). + to have_content "Hello, you've been invited to a phone interview" + end + end + + scenario 'with invalid data' do + login_with_admin_user + + visit '/v2/event_invitations/new' + + click_button 'Send invitation' + + expect(page).to have_text('There were problems with some of the fields.') + end +end + +def login_with_admin_user + user = FactoryGirl.create(:user) + visit '/users/sign_in' + fill_in 'Email', with: user.email + fill_in 'Password', with: user.password + click_button 'Sign in' +end diff --git a/spec/features/person_responds_to_interview_invitation_spec.rb b/spec/features/person_responds_to_interview_invitation_spec.rb new file mode 100644 index 000000000..63beb79ea --- /dev/null +++ b/spec/features/person_responds_to_interview_invitation_spec.rb @@ -0,0 +1,47 @@ +require 'rails_helper' +require 'capybara/email/rspec' + +feature 'Person responds to interview invitation' do + before do + clear_emails + @event = FactoryGirl.create(:event) + @person = FactoryGirl.create(:person) + + EventInvitationMailer.invite(email_address: @person.email_address, event: @event).deliver_now + + open_email(@person.email_address) + + current_email.click_link 'Please click to setup a time for your interview' + end + + scenario 'over email, successfully' do + @event.time_slots.each do |time| + expect(page).to have_content time.to_time_and_weekday + end + + find('#v2_reservation_time_slot_id_1').set(true) + + click_button 'Confirm reservation' + + selected_time = @event.time_slots.first.to_weekday_and_time + + expect(page).to have_content "An interview has been booked for #{selected_time}" + + admin_email = 'admin@what.host.should.we.have.here.com' + research_subject_email = @person.email_address + + [admin_email, research_subject_email].each do |email_address| + open_email(email_address) + + # TODO: substitute placeholder text + expect(current_email). + to have_content "An interview has been booked for #{selected_time}" + end + end + + scenario 'over email, but forgetting to select a time' do + click_button 'Confirm reservation' + + expect(page).to have_content "No time slot was selected, couldn't create the reservation" + end +end diff --git a/spec/mailers/event_invitation_mailer_spec.rb b/spec/mailers/event_invitation_mailer_spec.rb new file mode 100644 index 000000000..93779e207 --- /dev/null +++ b/spec/mailers/event_invitation_mailer_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +describe EventInvitationMailer, type: :mailer do +end diff --git a/spec/mailers/previews/event_invitation_mailer_preview.rb b/spec/mailers/previews/event_invitation_mailer_preview.rb new file mode 100644 index 000000000..bcfbc7998 --- /dev/null +++ b/spec/mailers/previews/event_invitation_mailer_preview.rb @@ -0,0 +1,4 @@ +# Preview all emails at http://localhost:3000/rails/mailers/event_invitation_mailer +class EventInvitationMailerPreview < ActionMailer::Preview + +end diff --git a/spec/models/v2/event_invitation_spec.rb b/spec/models/v2/event_invitation_spec.rb new file mode 100644 index 000000000..0d9fb52a0 --- /dev/null +++ b/spec/models/v2/event_invitation_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +describe V2::EventInvitation do + it { is_expected.to validate_presence_of(:email_address) } + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:slot_length) } + it { is_expected.to validate_presence_of(:date) } + it { is_expected.to validate_presence_of(:start_time) } + it { is_expected.to validate_presence_of(:end_time) } + + describe '#save' do + describe 'when valid' do + let(:args) do + { + email_address: 'some@email.com', + description: 'lorem', + slot_length: '45 mins', + date: '03/20/2016', + start_time: '15:00', + end_time: '16:30' + } + end + + subject { described_class.new(args) } + + it 'creates a new event' do + expect { subject.save }.to change { V2::Event.count }.from(0).to(1) + end + + it 'creates a new time slots' do + expect { subject.save }.to change { V2::TimeSlot.count }.from(0).to(2) + end + end + + describe 'with missing data' do + it 'returns false' do + expect(subject.save).to eql false + end + end + end +end diff --git a/spec/models/v2/event_spec.rb b/spec/models/v2/event_spec.rb new file mode 100644 index 000000000..3eec5c55a --- /dev/null +++ b/spec/models/v2/event_spec.rb @@ -0,0 +1,6 @@ +require 'rails_helper' + +describe V2::Event do + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:time_slots) } +end diff --git a/spec/models/v2/reservation_spec.rb b/spec/models/v2/reservation_spec.rb new file mode 100644 index 000000000..52528aded --- /dev/null +++ b/spec/models/v2/reservation_spec.rb @@ -0,0 +1,6 @@ +require 'rails_helper' + +describe V2::Reservation do + it { is_expected.to validate_presence_of(:person) } + it { is_expected.to validate_presence_of(:time_slot) } +end diff --git a/spec/models/v2/time_slot_spec.rb b/spec/models/v2/time_slot_spec.rb new file mode 100644 index 000000000..76fe80460 --- /dev/null +++ b/spec/models/v2/time_slot_spec.rb @@ -0,0 +1,6 @@ +require 'rails_helper' + +describe V2::TimeSlot do + it { is_expected.to validate_presence_of(:start_time) } + it { is_expected.to validate_presence_of(:end_time) } +end diff --git a/spec/models/v2/time_window_spec.rb b/spec/models/v2/time_window_spec.rb new file mode 100644 index 000000000..18d1c60cd --- /dev/null +++ b/spec/models/v2/time_window_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +describe V2::TimeWindow do + describe '#slots' do + subject do + described_class.new( + date: '06/12/2016', + start_time: '09:30', + end_time: '10:30', + slot_length: '30 mins' + ).slots + end + + it 'returns 2 time slots' do + expect(subject.count).to eql 2 + end + + it 'returns a time slot from 09:30 to 10:00' do + expect( + subject.find do |time_slot| + time_slot.start_time == Time.zone.parse('12/06/2016 09:30') && + time_slot.end_time == Time.zone.parse('12/06/2016 10:00') + end + ).to_not be_nil + end + + it 'returns a time slot from 10:00 to 10:30' do + expect( + subject.find do |time_slot| + time_slot.start_time == Time.zone.parse('12/06/2016 10:00') && + time_slot.end_time == Time.zone.parse('12/06/2016 10:30') + end + ).to_not be_nil + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 2a70a15c3..853a53396 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -4,6 +4,7 @@ require 'rspec/rails' require 'spec_helper' require 'shoulda/matchers' +require 'database_cleaner' ActiveRecord::Migration.maintain_test_schema! @@ -18,17 +19,27 @@ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/test/fixtures" - # If you're not using ActiveRecord, or you'd prefer not to run each of your - # examples within a transaction, remove the following line or assign false - # instead of true. - config.use_transactional_fixtures = true + config.use_transactional_fixtures = false - # The different available types are documented in the features, such as in - # https://relishapp.com/rspec/rspec-rails/docs config.infer_spec_type_from_file_location! - # Filter lines from Rails gems in backtraces. config.filter_rails_from_backtrace! - # arbitrary gems may also be filtered via: - # config.filter_gems_from_backtrace("gem name") + + config.use_transactional_fixtures = false + + config.before(:suite) do + DatabaseCleaner.clean_with(:truncation) + end + + config.before(:each) do + DatabaseCleaner.strategy = :transaction + end + + config.before(:each) do + DatabaseCleaner.start + end + + config.append_after(:each) do + DatabaseCleaner.clean + end end