<commit_before>class Event < ActiveRecord::Base

  strip_attributes
  def self.significant_attributes
    # i.e. if any of these attributes change, attendees should be notified and may need to cancel
    [:start, :finish, :name, :description, :address, :lat, :lng]
  end
  def changed_significantly?
    self.class.significant_attributes.each do |attr|
      self.send("#{attr}=", nil) if self.send(attr).blank? # this is needed because strip_attributes nullifies, but only before validation
      return true if self.send("#{attr}_changed?")
    end
    false
  end

  enum status: [:proposed, :approved, :cancelled]

  validate :must_not_be_empty
  def must_not_be_empty
    if start.blank? && name.blank? && description.blank? && address.blank? && !coords
      errors.add(:base, 'An event must have a name, description, date, or address')
    end
  end

  validate :must_start_before_finish
  validates :finish, presence: true, if: "start.present?"
  def must_start_before_finish
    if start.present? && finish.present? && start > finish
      errors.add(:finish, 'must be after the start')
    end
  end

  before_validation do |event|
    event.finish = nil if event.start.blank?
    # don't allow saving with only one of lat, lng; must be neither or both
    event.lat = nil if event.lng.blank?
    event.lng = nil if event.lat.blank?
  end

  # allow for adding a reason for cancelling an event, with separate fields for admin/coordinator and participants
  attr_accessor :cancel_notes, :cancel_description
  def cancel_notes=(str)
    str = str.strip unless str.nil?
    if str.present?
      self.notes = "Cancelled because: #{str}\n\n#{notes}".strip
      @cancel_notes = str
    end
  end
  def cancel_description=(str)
    str = str.strip unless str.nil?
    if str.present?
      self.description = "Cancelled because: #{str}\n\n#{description}".strip
      @cancel_description = str
    end
  end

  attr_accessor :notify_of_changes # setting to false allows supressing email notifications; todo: move to controller

  has_many :event_users, dependent: :destroy
  has_many :participants, -> { where "event_users.status = ?", EventUser.statuses[:attending] }, through: :event_users, source: :user
  belongs_to :coordinator, class_name: 'User'
  def users
    people = participants.to_a
    people << coordinator if coordinator
    people
  end

  # this section identical to that in model user.rb
  acts_as_mappable
  attr_accessor :no_geocode # force geocoding to not happen. used for testing
  after_validation :geocode, if: "!no_geocode && address_changed? && address.present? && (lat.blank? || lng.blank?)"
  validates :lat, numericality: {greater_than_or_equal_to: -90, less_than_or_equal_to: 90}, allow_nil: true
  validates :lng, numericality: {greater_than_or_equal_to: -180, less_than_or_equal_to: 180}, allow_nil: true

  default_scope { order :start }
  scope :with_date, -> { where 'start IS NOT NULL AND finish IS NOT NULL' }
  scope :past, -> { where('finish < ?', Time.zone.now).reorder('finish DESC') }
  scope :not_past, -> { where 'start IS NULL OR finish > ?', Time.zone.now }
  scope :not_attended_by, ->(user) { joins('LEFT JOIN event_users ON events.id = event_users.event_id').where("events.id NOT IN (SELECT event_id FROM event_users WHERE user_id = ?) AND coordinator_id != ?", user.id, user.id).distinct }
  scope :coordinatorless, -> { where coordinator: nil }
  scope :dateless, -> { where start: nil }
  scope :participatable, -> { where 'start IS NOT NULL AND coordinator_id IS NOT NULL AND events.status = ?', statuses[:approved] }
  scope :not_cancelled, -> { where 'events.status != ?', statuses[:cancelled] }
  scope :awaiting_approval, -> { not_past.where 'events.status = ? AND coordinator_id IS NOT NULL AND start IS NOT NULL', statuses[:proposed] }
  scope :in_month, ->(year, month) {
    month ||= ''
    year ||= ''
    unless (1..12).include?(month.to_i) && year.to_s.length == 4
      month = Time.zone.now.month
      year = Time.zone.now.year
    end
    start = Time.zone.parse "#{year}-#{month}-01"
    finish = month.to_i < 12 ? start.change(month: (start.month + 1)) : start.change(month: 1, year: (start.year + 1))
    where("start >= ? AND finish < ?", start, finish)
  }

  attr_reader :start_day, :start_time_12, :start_time_p
  attr_accessor :time_error
  before_validation do |event|
    errors.add(:base, 'Start time must be in the format ##:##') if event.time_error
  end
  def assign_attributes(attrs)
    # if inputing start in parts (day and time), then combine them
    start_day = attrs.delete(:start_day)
    start_time = attrs.delete(:start_time_12)
    if start_time.present? && !(start_time.strip =~ /1?[0-9](:[0-9]{2})?/)
      start_time = nil
      self.time_error = true
    end
    start_time_p = (attrs.delete(:start_time_p) || 'AM').upcase.gsub('.', '')
    attrs[:start] = "#{start_day} #{start_time} #{start_time_p}" if attrs[:start].blank? && start_day.present? && start_time.present?
    # this is needed because during mass assignment, we can't guarantee that :start will be set before :duration
    dur = attrs.delete(:duration)
    super(attrs)
    self.duration = dur
  end
  def start_time_12 # start time using 12-hour clock
    start.present? ? start.strftime('%l:%M') : nil
  end
  def start_time_p
    start.present? ? start.strftime('%p') : nil
  end<commit_msg>fixing bug in sql for events within a month. affected only the calendar view<commit_after>class Event < ActiveRecord::Base

  strip_attributes
  def self.significant_attributes
    # i.e. if any of these attributes change, attendees should be notified and may need to cancel
    [:start, :finish, :name, :description, :address, :lat, :lng]
  end
  def changed_significantly?
    self.class.significant_attributes.each do |attr|
      self.send("#{attr}=", nil) if self.send(attr).blank? # this is needed because strip_attributes nullifies, but only before validation
      return true if self.send("#{attr}_changed?")
    end
    false
  end

  enum status: [:proposed, :approved, :cancelled]

  validate :must_not_be_empty
  def must_not_be_empty
    if start.blank? && name.blank? && description.blank? && address.blank? && !coords
      errors.add(:base, 'An event must have a name, description, date, or address')
    end
  end

  validate :must_start_before_finish
  validates :finish, presence: true, if: "start.present?"
  def must_start_before_finish
    if start.present? && finish.present? && start > finish
      errors.add(:finish, 'must be after the start')
    end
  end

  before_validation do |event|
    event.finish = nil if event.start.blank?
    # don't allow saving with only one of lat, lng; must be neither or both
    event.lat = nil if event.lng.blank?
    event.lng = nil if event.lat.blank?
  end

  # allow for adding a reason for cancelling an event, with separate fields for admin/coordinator and participants
  attr_accessor :cancel_notes, :cancel_description
  def cancel_notes=(str)
    str = str.strip unless str.nil?
    if str.present?
      self.notes = "Cancelled because: #{str}\n\n#{notes}".strip
      @cancel_notes = str
    end
  end
  def cancel_description=(str)
    str = str.strip unless str.nil?
    if str.present?
      self.description = "Cancelled because: #{str}\n\n#{description}".strip
      @cancel_description = str
    end
  end

  attr_accessor :notify_of_changes # setting to false allows supressing email notifications; todo: move to controller

  has_many :event_users, dependent: :destroy
  has_many :participants, -> { where "event_users.status = ?", EventUser.statuses[:attending] }, through: :event_users, source: :user
  belongs_to :coordinator, class_name: 'User'
  def users
    people = participants.to_a
    people << coordinator if coordinator
    people
  end

  # this section identical to that in model user.rb
  acts_as_mappable
  attr_accessor :no_geocode # force geocoding to not happen. used for testing
  after_validation :geocode, if: "!no_geocode && address_changed? && address.present? && (lat.blank? || lng.blank?)"
  validates :lat, numericality: {greater_than_or_equal_to: -90, less_than_or_equal_to: 90}, allow_nil: true
  validates :lng, numericality: {greater_than_or_equal_to: -180, less_than_or_equal_to: 180}, allow_nil: true

  default_scope { order :start }
  scope :with_date, -> { where 'start IS NOT NULL AND finish IS NOT NULL' }
  scope :past, -> { where('finish < ?', Time.zone.now).reorder('finish DESC') }
  scope :not_past, -> { where 'start IS NULL OR finish > ?', Time.zone.now }
  scope :not_attended_by, ->(user) { joins('LEFT JOIN event_users ON events.id = event_users.event_id').where("events.id NOT IN (SELECT event_id FROM event_users WHERE user_id = ?) AND coordinator_id != ?", user.id, user.id).distinct }
  scope :coordinatorless, -> { where coordinator: nil }
  scope :dateless, -> { where start: nil }
  scope :participatable, -> { where 'start IS NOT NULL AND coordinator_id IS NOT NULL AND events.status = ?', statuses[:approved] }
  scope :not_cancelled, -> { where 'events.status != ?', statuses[:cancelled] }
  scope :awaiting_approval, -> { not_past.where 'events.status = ? AND coordinator_id IS NOT NULL AND start IS NOT NULL', statuses[:proposed] }
  scope :in_month, ->(year, month) { # can pass in integers or strings which are integers
    month ||= ''
    year ||= ''
    unless (1..12).include?(month.to_i) && year.to_s.length == 4
      month = Time.zone.now.month
      year = Time.zone.now.year
    end
    start = Time.zone.parse "#{year}-#{month}-01"
    finish = month.to_i < 12 ? start.change(month: (start.month + 1)) : start.change(month: 1, year: (start.year + 1))
    where("start >= ? AND start < ?", start, finish)
  }

  attr_reader :start_day, :start_time_12, :start_time_p
  attr_accessor :time_error
  before_validation do |event|
    errors.add(:base, 'Start time must be in the format ##:##') if event.time_error
  end
  def assign_attributes(attrs)
    # if inputing start in parts (day and time), then combine them
    start_day = attrs.delete(:start_day)
    start_time = attrs.delete(:start_time_12)
    if start_time.present? && !(start_time.strip =~ /1?[0-9](:[0-9]{2})?/)
      start_time = nil
      self.time_error = true
    end
    start_time_p = (attrs.delete(:start_time_p) || 'AM').upcase.gsub('.', '')
    attrs[:start] = "#{start_day} #{start_time} #{start_time_p}" if attrs[:start].blank? && start_day.present? && start_time.present?
    # this is needed because during mass assignment, we can't guarantee that :start will be set before :duration
    dur = attrs.delete(:duration)
    super(attrs)
    self.duration = dur
  end
  def start_time_12 # start time using 12-hour clock
    start.present? ? start.strftime('%l:%M') : nil
  end
  def start_time_p
    start.present? ? start.strftime('%p') : nil
  end
{'fraction_non_alphanumeric': 0.12286689419795221, 'fraction_numerical': 0.006456968914306798, 'mean_word_length': 3.8293986636971047, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5281934', 'n_tokens_mistral': 3568, 'n_tokens_neox': 3461, 'n_words': 1019}