diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 84a7ba1d3..1cde6b50f 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -56,7 +56,7 @@ def deactivate redirect_to @category elsif params[:deactivation_confirmed] @category.equipment_models.each do |em| - Reservation.for_eq_model(em).finalized.each do |r| + Reservation.for_eq_model(em.id).finalized.each do |r| r.archive(current_user, 'The category was deactivated.') .save(validate: false) end diff --git a/app/controllers/equipment_models_controller.rb b/app/controllers/equipment_models_controller.rb index d924d2ab2..3e0cb59e7 100644 --- a/app/controllers/equipment_models_controller.rb +++ b/app/controllers/equipment_models_controller.rb @@ -31,7 +31,8 @@ def index end def show # rubocop:disable AbcSize, MethodLength - relevant_reservations = Reservation.active.for_eq_model(@equipment_model) + relevant_reservations = + Reservation.for_eq_model(@equipment_model.id).active @associated_equipment_models = @equipment_model.associated_equipment_models.sample(6) @@ -59,9 +60,11 @@ def show # rubocop:disable AbcSize, MethodLength @restricted = @equipment_model.model_restricted?(cart.reserver_id) # For pending reservations table - @pending = relevant_reservations.reserved_in_date_range(Time.zone.today, - Time.zone.today + - 8.days).reserved + @pending = + relevant_reservations.reserved_in_date_range(Time.zone.today, + Time.zone.today + 8.days) + # Future reservations using Query object + @future = @pending.future end def new @@ -124,7 +127,7 @@ def deactivate flash[:notice] = 'Deactivation cancelled.' redirect_to @equipment_model elsif params[:deactivation_confirmed] - Reservation.for_eq_model(@equipment_model).finalized.each do |r| + Reservation.for_eq_model(@equipment_model.id).finalized.each do |r| r.archive(current_user, 'The equipment model was deactivated.') .save(validate: false) end diff --git a/app/controllers/reservations_controller.rb b/app/controllers/reservations_controller.rb index 5e7775f5c..fd2837de3 100644 --- a/app/controllers/reservations_controller.rb +++ b/app/controllers/reservations_controller.rb @@ -82,7 +82,6 @@ def index end set_counts(source, time) - @reservations_set = time.send(@filter) end diff --git a/app/decorators/category_decorator.rb b/app/decorators/category_decorator.rb index ae78ff9e2..0b116f478 100644 --- a/app/decorators/category_decorator.rb +++ b/app/decorators/category_decorator.rb @@ -15,7 +15,7 @@ def make_deactivate_btn # find reservations for models in the category in the next week res = 0 object.equipment_models.each do |em| - res += Reservation.for_eq_model(em).finalized + res += Reservation.for_eq_model(em.id).finalized .reserved_in_date_range(Time.zone.today - 1.day, Time.zone.today + 7.days) .count diff --git a/app/decorators/equipment_model_decorator.rb b/app/decorators/equipment_model_decorator.rb index 68405475c..f619adb57 100644 --- a/app/decorators/equipment_model_decorator.rb +++ b/app/decorators/equipment_model_decorator.rb @@ -13,7 +13,7 @@ class EquipmentModelDecorator < ApplicationDecorator def make_deactivate_btn unless object.deleted_at # find reservations in the next week - res = Reservation.for_eq_model(object).finalized + res = Reservation.for_eq_model(object.id).finalized .reserved_in_date_range(Time.zone.today - 1.day, Time.zone.today + 7.days) .count diff --git a/app/models/cart_validations.rb b/app/models/cart_validations.rb index 662fc3404..5791a492e 100644 --- a/app/models/cart_validations.rb +++ b/app/models/cart_validations.rb @@ -127,7 +127,7 @@ def check_max_cat def check_availability(model = EquipmentModel.find(items.keys.first), quantity = 1, - source_res = Reservation.for_eq_model(self) + source_res = Reservation.for_eq_model(id) .active.all) # checks that the model is available for the given quantity given the diff --git a/app/models/equipment_model.rb b/app/models/equipment_model.rb index 0cad1e0c1..be4b91965 100644 --- a/app/models/equipment_model.rb +++ b/app/models/equipment_model.rb @@ -191,7 +191,7 @@ def num_available_from_source(start_date, due_date, source_reservations) def num_available(start_date, due_date) # for if you just want the number available, 1 query to get # relevant reservations - relevant_reservations = Reservation.for_eq_model(self).finalized + relevant_reservations = Reservation.for_eq_model(id).finalized .reserved_in_date_range(start_date, due_date) .all num_available_from_source(start_date, due_date, relevant_reservations) @@ -207,14 +207,14 @@ def model_restricted?(reserver_id) # Returns the number of overdue objects for a given model, # as long as they have been checked out. def number_overdue - Reservation.overdue.for_eq_model(self).size + Reservation.overdue.for_eq_model(id).size end def available_count(date) # get the total number of items of this kind then subtract the total # quantity currently reserved, checked-out, and overdue total = equipment_items.active.count - reserved = Reservation.reserved_on_date(date).for_eq_model(self).count + reserved = Reservation.reserved_on_date(date).for_eq_model(id).count total - reserved - number_overdue end diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 03c3683fa..e1813f68f 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -1,8 +1,7 @@ -# rubocop:disable ClassLength +# rubocop:disable Metrics/ClassLength, Rails/ScopeArgs class Reservation < ActiveRecord::Base include Linkable include ReservationValidations - include ReservationScopes belongs_to :equipment_model belongs_to :equipment_item @@ -37,6 +36,60 @@ class Reservation < ActiveRecord::Base fined: (1 << 4), missed_email_sent: (1 << 5), expired: (1 << 6) } + ## Scopes ## + # general scopes + default_scope { order('start_date, due_date, reserver_id') } + scope :for_eq_model, ->(em_id) { where(equipment_model_id: em_id) } + scope :for_reserver, ->(reserver_id) { where(reserver_id: reserver_id) } + + # flag scopes + scope :flagged, ->(flag) { where('flags & ? > 0', FLAGS[flag]) } + scope :not_flagged, ->(flag) { where('flags & ? = 0', FLAGS[flag]) } + + # basic status scopes + scope :active, lambda { + where(status: Reservation.statuses.values_at(*%w(reserved checked_out))) + } + scope :finalized, lambda { + where.not(status: Reservation.statuses.values_at(*%w(denied requested))) + } + scope :active_or_requested, lambda { + where(status: Reservation.statuses.values_at( + *%w(requested reserved checked_out))) + } + + # overdue / request scopes + scope :overdue, ->() { where(overdue: true).checked_out } + scope :returned_on_time, ->() { where(overdue: false).returned } + scope :returned_overdue, ->() { where(overdue: true).returned } + scope :approved_requests, ->() { flagged(:request).finalized } + scope :missed_requests, ->() { past_date(:start_date).requested } + + # generalized date scopes (pass parameter as either a string or a symbol) + scope :past_date, ->(param) { where("#{param} < ?", Time.zone.today) } + scope :today_date, ->(param) { where(param.to_sym => Time.zone.today) } + + # basic date scopes + scope :checked_out_today, ->() { today_date(:checked_out) } + scope :checked_out_previous, ->() { past_date(:checked_out) } + scope :due_today, ->() { today_date(:due_date) } + + # more complex / task-specific scopes + scope :checkoutable, Reservations::CheckoutableQuery + scope :ends_on_days, Reservations::EndsOnDaysQuery + scope :future, Reservations::FutureQuery + scope :notes_unsent, Reservations::NotesUnsentQuery + scope :reserved_in_date_range, Reservations::ReservedInDateRangeQuery + scope :reserved_on_date, Reservations::ReservedOnDateQuery + scope :starts_on_days, Reservations::StartsOnDaysQuery + scope :upcoming, Reservations::UpcomingQuery + + # join Scopes + scope :with_categories, lambda { + joins(:equipment_model) + .select('reservations.*, equipment_models.category_id as category_id') + } + ## Class methods ## def self.completed_procedures(procedures) diff --git a/app/models/reservation_scopes.rb b/app/models/reservation_scopes.rb deleted file mode 100644 index a9b3d0f0f..000000000 --- a/app/models/reservation_scopes.rb +++ /dev/null @@ -1,88 +0,0 @@ -module ReservationScopes - def self.included(base) # rubocop:disable MethodLength, AbcSize - base.class_eval do - default_scope { order('start_date, due_date, reserver_id') } - scope :user_sort, ->() { order('reserver_id') } - - scope :flagged, lambda { |flag| - where('flags & ? > 0', Reservation::FLAGS[flag]) - } - - scope :not_flagged, lambda { |flag| - where('flags & ? = 0', Reservation::FLAGS[flag]) - } - - scope :active, lambda { - where(status: Reservation.statuses.values_at( - *%w(reserved checked_out))) - } - - scope :finalized, lambda { - where.not(status: Reservation.statuses.values_at(*%w(denied requested))) - } - - scope :checked_out_today, lambda { - where(checked_out: Time.zone.today) - } - scope :checked_out_previous, lambda { - where('checked_out < ?', Time.zone.today) - } - scope :overdue, ->() { where(overdue: true).checked_out } - - scope :checked_in, ->() { returned } - - scope :returned_on_time, ->() { where(overdue: false).returned } - scope :returned_overdue, ->() { where(overdue: true).returned } - scope :upcoming, lambda { - unscoped.where(start_date: Time.zone.today).reserved.user_sort - } - scope :due_soon, lambda { - where(due_date: Time.zone.today) - } - scope :checkoutable, lambda { - where('start_date <= ?', Time.zone.today).reserved - } - scope :future, lambda { - where('start_date > ?', Time.zone.today.to_time).reserved - } - scope :starts_on_days, lambda { |start_date, end_date| - where(start_date: start_date..end_date) - } - scope :ends_on_days, lambda { |start_date, end_date| - where(due_date: start_date..end_date) - } - scope :reserved_on_date, lambda { |date| - overlaps_with_date(date).active - } - scope :for_eq_model, lambda { |eq_model| - where(equipment_model_id: eq_model.id) - } - scope :active_or_requested, lambda { - where(status: Reservation.statuses.values_at( - *%w(requested reserved checked_out))) - } - scope :notes_unsent, ->() { where(notes_unsent: true) } - - scope :approved_requests, lambda { - flagged(:request).finalized - } - - scope :missed_requests, lambda { - where('start_date < ?', Time.zone.today).requested - } - scope :for_reserver, ->(reserver) { where(reserver_id: reserver) } - scope :reserved_in_date_range, lambda { |start_date, end_date| - where('start_date <= ? and due_date >= ?', end_date, start_date) - .reserved - } - scope :overlaps_with_date, lambda { |date| - where('start_date <= ? and due_date >= ?', date, date) - } - scope :has_notes, ->() { where.not(notes: nil) } - scope :with_categories, lambda { - joins(:equipment_model) - .select('reservations.*, equipment_models.category_id as category_id') - } - end - end -end diff --git a/app/queries/query_base.rb b/app/queries/query_base.rb new file mode 100644 index 000000000..fdea62f00 --- /dev/null +++ b/app/queries/query_base.rb @@ -0,0 +1,13 @@ +class QueryBase + class << self + delegate :call, to: :new + end + + def initialize + fail NotImplementedError + end + + def call + fail NotImplementedError + end +end diff --git a/app/queries/reservations/checkoutable_query.rb b/app/queries/reservations/checkoutable_query.rb new file mode 100644 index 000000000..37eba9cdf --- /dev/null +++ b/app/queries/reservations/checkoutable_query.rb @@ -0,0 +1,7 @@ +module Reservations + class CheckoutableQuery < Reservations::ReservationsQueryBase + def call + @relation.where('start_date <= ?', Time.zone.today).reserved + end + end +end diff --git a/app/queries/reservations/ends_on_days_query.rb b/app/queries/reservations/ends_on_days_query.rb new file mode 100644 index 000000000..2494af140 --- /dev/null +++ b/app/queries/reservations/ends_on_days_query.rb @@ -0,0 +1,7 @@ +module Reservations + class EndsOnDaysQuery < Reservations::ReservationsQueryBase + def call(start_date, end_date) + @relation.where(due_date: start_date..end_date) + end + end +end diff --git a/app/queries/reservations/future_query.rb b/app/queries/reservations/future_query.rb new file mode 100644 index 000000000..0ccda1227 --- /dev/null +++ b/app/queries/reservations/future_query.rb @@ -0,0 +1,7 @@ +module Reservations + class FutureQuery < Reservations::ReservationsQueryBase + def call + @relation.where('start_date > ?', Time.zone.today.to_time).reserved + end + end +end diff --git a/app/queries/reservations/notes_unsent_query.rb b/app/queries/reservations/notes_unsent_query.rb new file mode 100644 index 000000000..ebab4991a --- /dev/null +++ b/app/queries/reservations/notes_unsent_query.rb @@ -0,0 +1,7 @@ +module Reservations + class NotesUnsentQuery < Reservations::ReservationsQueryBase + def call + @relation.where(notes_unsent: true).where.not(notes: nil) + end + end +end diff --git a/app/queries/reservations/reservations_query_base.rb b/app/queries/reservations/reservations_query_base.rb new file mode 100644 index 000000000..d95d8d085 --- /dev/null +++ b/app/queries/reservations/reservations_query_base.rb @@ -0,0 +1,7 @@ +module Reservations + class ReservationsQueryBase < QueryBase + def initialize(relation = Reservation.all) + @relation = relation + end + end +end diff --git a/app/queries/reservations/reserved_in_date_range_query.rb b/app/queries/reservations/reserved_in_date_range_query.rb new file mode 100644 index 000000000..f186742e3 --- /dev/null +++ b/app/queries/reservations/reserved_in_date_range_query.rb @@ -0,0 +1,9 @@ +module Reservations + class ReservedInDateRangeQuery < Reservations::ReservationsQueryBase + def call(start_date, end_date) + @relation + .where('start_date <= ? and due_date >= ?', end_date, start_date) + .reserved + end + end +end diff --git a/app/queries/reservations/reserved_on_date_query.rb b/app/queries/reservations/reserved_on_date_query.rb new file mode 100644 index 000000000..2e31f143f --- /dev/null +++ b/app/queries/reservations/reserved_on_date_query.rb @@ -0,0 +1,7 @@ +module Reservations + class ReservedOnDateQuery < Reservations::ReservationsQueryBase + def call(date) + @relation.where('start_date <= ? and due_date >= ?', date, date).active + end + end +end diff --git a/app/queries/reservations/starts_on_days_query.rb b/app/queries/reservations/starts_on_days_query.rb new file mode 100644 index 000000000..1a9ffe14a --- /dev/null +++ b/app/queries/reservations/starts_on_days_query.rb @@ -0,0 +1,7 @@ +module Reservations + class StartsOnDaysQuery < Reservations::ReservationsQueryBase + def call(start_date, end_date) + @relation.where(start_date: start_date..end_date) + end + end +end diff --git a/app/queries/reservations/upcoming_query.rb b/app/queries/reservations/upcoming_query.rb new file mode 100644 index 000000000..bc552d09a --- /dev/null +++ b/app/queries/reservations/upcoming_query.rb @@ -0,0 +1,7 @@ +module Reservations + class UpcomingQuery < Reservations::ReservationsQueryBase + def call + @relation.unscoped.today_date(:start_date).reserved.order('reserver_id') + end + end +end diff --git a/app/views/equipment_models/show.html.erb b/app/views/equipment_models/show.html.erb index 8b044e493..6ebb07016 100644 --- a/app/views/equipment_models/show.html.erb +++ b/app/views/equipment_models/show.html.erb @@ -165,7 +165,7 @@ <%= @pending.checkoutable.count %>
- <%= @pending.future.count %> + <%= @future.count %>
<% if @pending.length > 0 %> diff --git a/lib/tasks/email_checkin_reminder.rake b/lib/tasks/email_checkin_reminder.rake index 69044dff0..2ca1917a2 100644 --- a/lib/tasks/email_checkin_reminder.rake +++ b/lib/tasks/email_checkin_reminder.rake @@ -2,7 +2,7 @@ desc 'Send email reminder about upcoming check-ins' task email_checkin_reminder: :environment do if AppConfig.first.upcoming_checkin_email_active? # get all reservations that end today and aren't already checked in - upcoming_reservations = Reservation.due_soon + upcoming_reservations = Reservation.due_today Rails.logger.info "Found #{upcoming_reservations.size} reservations due "\ 'for check-in. Sending reminder emails...' upcoming_reservations.each do |upcoming_reservation| diff --git a/lib/tasks/email_notes_to_admins.rake b/lib/tasks/email_notes_to_admins.rake index 3292df8ba..953f36ca3 100644 --- a/lib/tasks/email_notes_to_admins.rake +++ b/lib/tasks/email_notes_to_admins.rake @@ -2,8 +2,8 @@ desc 'Send email to admins on reservations with notes' task email_notes_to_admins: :environment do # gets all reservations with notes and sends an email to the admin of the # application, to alert them. - notes_reservations_out = Reservation.has_notes.checked_out.notes_unsent - notes_reservations_in = Reservation.has_notes.checked_in.notes_unsent + notes_reservations_out = Reservation.checked_out.notes_unsent + notes_reservations_in = Reservation.returned.notes_unsent Rails.logger.info "Found #{notes_reservations_out.size} reservations "\ "checked out with notes and #{notes_reservations_in.size} "\ 'reservations checked in with notes.' diff --git a/spec/controllers/reservations_controller_spec.rb b/spec/controllers/reservations_controller_spec.rb index d13bb3661..7a3c74741 100644 --- a/spec/controllers/reservations_controller_spec.rb +++ b/spec/controllers/reservations_controller_spec.rb @@ -111,9 +111,10 @@ # Assertion and expectation @filters.each do |f| get :index, f => true - expect(assigns(:reservations_set).uniq.sort).to eq(Reservation.send(f) - .starts_on_days(assigns(:start_date), assigns(:end_date)) - .uniq.sort) + expect(assigns(:reservations_set).uniq.sort).to \ + eq(Reservation.send(f) + .starts_on_days(assigns(:start_date), assigns(:end_date)) + .uniq.sort) end end it 'populates with respect to session[:filter] first' do @@ -126,9 +127,10 @@ @filters.each do |f| session[:filter] = f.to_s get :index, @filters.sample => true - expect(assigns(:reservations_set).uniq.sort).to eq(Reservation.send(f) - .starts_on_days(assigns(:start_date), assigns(:end_date)) - .uniq.sort) + expect(assigns(:reservations_set).uniq.sort).to \ + eq(Reservation.send(f) + .starts_on_days(assigns(:start_date), assigns(:end_date)) + .uniq.sort) end end diff --git a/spec/features/reservations_spec.rb b/spec/features/reservations_spec.rb index e170fb735..ca476b3a5 100644 --- a/spec/features/reservations_spec.rb +++ b/spec/features/reservations_spec.rb @@ -30,7 +30,8 @@ click_button 'Finalize Reservation' # check that reservation was created with correct dates - query = Reservation.for_eq_model(@eq_model).for_reserver(reserver.id) + query = + Reservation.for_eq_model(@eq_model.id).for_reserver(reserver.id) expect(query.count).to eq(1) expect(query.first.start_date).to eq(Time.zone.today) expect(query.first.due_date).to eq(due_date) @@ -67,7 +68,8 @@ click_button 'Submit Request' # check that reservation request was created with correct dates - query = Reservation.for_eq_model(@eq_model).for_reserver(reserver.id) + query = + Reservation.for_eq_model(@eq_model.id).for_reserver(reserver.id) expect(query.count).to eq(1) expect(query.first.start_date).to eq(Time.zone.today) expect(query.first.due_date).to eq(bad_due_date) @@ -105,7 +107,8 @@ click_button 'Finalize Reservation' # check that reservation was created with correct dates - query = Reservation.for_eq_model(@eq_model).for_reserver(reserver.id) + query = + Reservation.for_eq_model(@eq_model.id).for_reserver(reserver.id) expect(query.count).to eq(1) expect(query.first.start_date).to eq(Time.zone.today) expect(query.first.due_date).to eq(bad_due_date)