<commit_before># coding: utf-8
class Project < ActiveRecord::Base
  include PgSearch

  include Shared::CatarseAutoHtml
  include Shared::StateMachineHelpers
  include Shared::Queued

  include Project::StateMachineHandler
  include Project::VideoHandler
  include Project::CustomValidators
  include Project::RemindersHandler
  include Project::ErrorGroups
  include Project::PaymentEngineHandler

  has_notifications

  mount_uploader :uploaded_image, ProjectUploader

  delegate  :display_online_date, :display_status, :progress, :display_progress,
            :display_image, :display_expires_at, :remaining_text, :time_to_go,
            :display_pledged, :display_goal, :remaining_days, :progress_bar,
            :status_flag, :state_warning_template, :display_card_class, :display_errors, to: :decorator

  belongs_to :user
  belongs_to :category
  has_one :project_total
  has_one :account, class_name: "ProjectAccount", inverse_of: :project
  has_many :rewards
  has_many :contributions
  has_many :posts, class_name: "ProjectPost", inverse_of: :project
  has_many :budgets, class_name: "ProjectBudget", inverse_of: :project
  has_many :unsubscribes

  accepts_nested_attributes_for :rewards, allow_destroy: true, reject_if: -> (x) { x[:description].blank? || x[:minimum_value].blank? }
  accepts_nested_attributes_for :user
  accepts_nested_attributes_for :account
  accepts_nested_attributes_for :posts, allow_destroy: true#, reject_if: ->(x) { x[:title].blank? || x[:comment].blank? }
  accepts_nested_attributes_for :budgets, allow_destroy: true, reject_if: ->(x) { x[:name].blank? || x[:value].blank? }

  catarse_auto_html_for field: :about, video_width: 600, video_height: 403

  pg_search_scope :pg_search,
    against: "full_text_index",
    using: {
      tsearch: {
        dictionary: "portuguese",
        tsvector_column: "full_text_index"
      }
    },
    ignoring: :accents

  # Used to simplify a has_scope
  scope :successful, ->{ with_state('successful') }
  scope :with_project_totals, -> { joins('LEFT OUTER JOIN project_totals ON project_totals.project_id = projects.id') }

  scope :by_progress, ->(progress) { joins(:project_total).where("project_totals.pledged >= projects.goal*?", progress.to_i/100.to_f) }
  scope :by_user_email, ->(email) { joins(:user).where("users.email = ?", email) }
  scope :by_id, ->(id) { where(id: id) }
  scope :by_goal, ->(goal) { where(goal: goal) }
  scope :by_category_id, ->(id) { where(category_id: id) }
  scope :by_online_date, ->(online_date) { where("online_date::date = ?", online_date.to_date) }
  scope :by_expires_at, ->(expires_at) { where("projects.expires_at::date = ?", expires_at.to_date) }
  scope :by_updated_at, ->(updated_at) { where("updated_at::date = ?", updated_at.to_date) }
  scope :by_permalink, ->(p) { without_state('deleted').where("lower(permalink) = lower(?)", p) }
  scope :recommended, -> { where(recommended: true) }
  scope :in_funding, -> { not_expired.with_states(['online']) }
  scope :name_contains, ->(term) { where("unaccent(upper(name)) LIKE ('%'||unaccent(upper(?))||'%')", term) }
  scope :user_name_contains, ->(term) { joins(:user).where("unaccent(upper(users.name)) LIKE ('%'||unaccent(upper(?))||'%')", term) }
  scope :near_of, ->(address_state) { where("EXISTS(SELECT true FROM users u WHERE u.id = projects.user_id AND lower(u.address_state) = lower(?))", address_state) }
  scope :to_finish, ->{ expired.with_states(['online', 'waiting_funds']) }
  scope :visible, -> { without_states(['draft', 'rejected', 'deleted', 'in_analysis', 'approved']) }
  scope :financial, -> { with_states(['online', 'successful', 'waiting_funds']).where("projects.expires_at > (current_timestamp - '15 days'::interval)") }
  scope :expired, -> { where("projects.expires_at < current_timestamp") }
  scope :not_expired, -> { where("projects.expires_at >= current_timestamp") }
  scope :expiring, -> { not_expired.where("projects.expires_at <= (current_timestamp + interval '2 weeks')") }
  scope :not_expiring, -> { not_expired.where("NOT (projects.expires_at <= (current_timestamp + interval '2 weeks'))") }
  scope :recent, -> { where("(current_timestamp - projects.online_date) <= '5 days'::interval") }
  scope :ordered, -> { order(created_at: :desc)}
  scope :order_status, ->{ order("
                                     CASE projects.state
                                     WHEN 'online' THEN 1
                                     WHEN 'waiting_funds' THEN 2
                                     WHEN 'successful' THEN 3
                                     WHEN 'failed' THEN 4
                                     END ASC")}
  scope :most_recent_first, ->{ order("projects.online_date DESC, projects.created_at DESC") }
  scope :order_for_admin, -> {
    reorder("
            CASE projects.state
            WHEN 'in_analysis' THEN 1
            WHEN 'waiting_funds' THEN 2
            WHEN 'successful' THEN 3
            WHEN 'failed' THEN 4
            END ASC, projects.online_date DESC, projects.created_at DESC")
  }

  scope :with_contributions_confirmed_today, -> {
    joins(:contributions).merge(Contribution.confirmed_today).uniq
  }

  scope :of_current_week, -> {
    where("
      projects.online_date AT TIME ZONE '#{Time.zone.tzinfo.name}' >= (current_timestamp AT TIME ZONE '#{Time.zone.tzinfo.name}' - '7 days'::interval)
    ")
  }

  attr_accessor :accepted_terms

  # Draft state validtions
  validates_acceptance_of :accepted_terms, on: :create
  validates_presence_of :name, :user, :category, :permalink
  validates_length_of :headline, maximum: 140
  validates_numericality_of :online_days, less_than_or_equal_to: 60, greater_than: 0, if: ->(p){ p.online_days.present? }
  validates_numericality_of :goal, greater_than: 9, allow_blank: true
  validates_uniqueness_of :permalink, case_sensitive: false
  validates_format_of :permalink, with: /\A(\w|-)*\Z/

  validates_with StateValidator

  [:between_created_at, :between_expires_at, :between_online_date, :between_updated_at].each do |name|
    define_singleton_method name do |starts_at, ends_at|
      between_dates name.to_s.gsub('between_',''), starts_at, ends_at
    end
  end

  def self.goal_between(starts_at, ends_at)
    where("goal BETWEEN ? AND ?", starts_at, ends_at)
  end

  def self.order_by(sort_field)
    return self.all unless sort_field =~ /^\w+(\.\w+)?\s(desc|asc)$/i
    order(sort_field)<commit_msg>Adds exception to project online_days validation based on a list of projects with extended deadlines<commit_after># coding: utf-8
class Project < ActiveRecord::Base
  include PgSearch

  include Shared::CatarseAutoHtml
  include Shared::StateMachineHelpers
  include Shared::Queued

  include Project::StateMachineHandler
  include Project::VideoHandler
  include Project::CustomValidators
  include Project::RemindersHandler
  include Project::ErrorGroups
  include Project::PaymentEngineHandler

  has_notifications

  mount_uploader :uploaded_image, ProjectUploader

  delegate  :display_online_date, :display_status, :progress, :display_progress,
            :display_image, :display_expires_at, :remaining_text, :time_to_go,
            :display_pledged, :display_goal, :remaining_days, :progress_bar,
            :status_flag, :state_warning_template, :display_card_class, :display_errors, to: :decorator

  belongs_to :user
  belongs_to :category
  has_one :project_total
  has_one :account, class_name: "ProjectAccount", inverse_of: :project
  has_many :rewards
  has_many :contributions
  has_many :posts, class_name: "ProjectPost", inverse_of: :project
  has_many :budgets, class_name: "ProjectBudget", inverse_of: :project
  has_many :unsubscribes

  accepts_nested_attributes_for :rewards, allow_destroy: true, reject_if: -> (x) { x[:description].blank? || x[:minimum_value].blank? }
  accepts_nested_attributes_for :user
  accepts_nested_attributes_for :account
  accepts_nested_attributes_for :posts, allow_destroy: true#, reject_if: ->(x) { x[:title].blank? || x[:comment].blank? }
  accepts_nested_attributes_for :budgets, allow_destroy: true, reject_if: ->(x) { x[:name].blank? || x[:value].blank? }

  catarse_auto_html_for field: :about, video_width: 600, video_height: 403

  pg_search_scope :pg_search,
    against: "full_text_index",
    using: {
      tsearch: {
        dictionary: "portuguese",
        tsvector_column: "full_text_index"
      }
    },
    ignoring: :accents

  # Used to simplify a has_scope
  scope :successful, ->{ with_state('successful') }
  scope :with_project_totals, -> { joins('LEFT OUTER JOIN project_totals ON project_totals.project_id = projects.id') }

  scope :by_progress, ->(progress) { joins(:project_total).where("project_totals.pledged >= projects.goal*?", progress.to_i/100.to_f) }
  scope :by_user_email, ->(email) { joins(:user).where("users.email = ?", email) }
  scope :by_id, ->(id) { where(id: id) }
  scope :by_goal, ->(goal) { where(goal: goal) }
  scope :by_category_id, ->(id) { where(category_id: id) }
  scope :by_online_date, ->(online_date) { where("online_date::date = ?", online_date.to_date) }
  scope :by_expires_at, ->(expires_at) { where("projects.expires_at::date = ?", expires_at.to_date) }
  scope :by_updated_at, ->(updated_at) { where("updated_at::date = ?", updated_at.to_date) }
  scope :by_permalink, ->(p) { without_state('deleted').where("lower(permalink) = lower(?)", p) }
  scope :recommended, -> { where(recommended: true) }
  scope :in_funding, -> { not_expired.with_states(['online']) }
  scope :name_contains, ->(term) { where("unaccent(upper(name)) LIKE ('%'||unaccent(upper(?))||'%')", term) }
  scope :user_name_contains, ->(term) { joins(:user).where("unaccent(upper(users.name)) LIKE ('%'||unaccent(upper(?))||'%')", term) }
  scope :near_of, ->(address_state) { where("EXISTS(SELECT true FROM users u WHERE u.id = projects.user_id AND lower(u.address_state) = lower(?))", address_state) }
  scope :to_finish, ->{ expired.with_states(['online', 'waiting_funds']) }
  scope :visible, -> { without_states(['draft', 'rejected', 'deleted', 'in_analysis', 'approved']) }
  scope :financial, -> { with_states(['online', 'successful', 'waiting_funds']).where("projects.expires_at > (current_timestamp - '15 days'::interval)") }
  scope :expired, -> { where("projects.expires_at < current_timestamp") }
  scope :not_expired, -> { where("projects.expires_at >= current_timestamp") }
  scope :expiring, -> { not_expired.where("projects.expires_at <= (current_timestamp + interval '2 weeks')") }
  scope :not_expiring, -> { not_expired.where("NOT (projects.expires_at <= (current_timestamp + interval '2 weeks'))") }
  scope :recent, -> { where("(current_timestamp - projects.online_date) <= '5 days'::interval") }
  scope :ordered, -> { order(created_at: :desc)}
  scope :order_status, ->{ order("
                                     CASE projects.state
                                     WHEN 'online' THEN 1
                                     WHEN 'waiting_funds' THEN 2
                                     WHEN 'successful' THEN 3
                                     WHEN 'failed' THEN 4
                                     END ASC")}
  scope :most_recent_first, ->{ order("projects.online_date DESC, projects.created_at DESC") }
  scope :order_for_admin, -> {
    reorder("
            CASE projects.state
            WHEN 'in_analysis' THEN 1
            WHEN 'waiting_funds' THEN 2
            WHEN 'successful' THEN 3
            WHEN 'failed' THEN 4
            END ASC, projects.online_date DESC, projects.created_at DESC")
  }

  scope :with_contributions_confirmed_today, -> {
    joins(:contributions).merge(Contribution.confirmed_today).uniq
  }

  scope :of_current_week, -> {
    where("
      projects.online_date AT TIME ZONE '#{Time.zone.tzinfo.name}' >= (current_timestamp AT TIME ZONE '#{Time.zone.tzinfo.name}' - '7 days'::interval)
    ")
  }

  attr_accessor :accepted_terms

  # Draft state validtions
  PROJECTS_WITH_EXTENDED_DEADLINE = [9431, 9317, 9493, 9845, 9643, 9524, 8552, 9353, 9746, 9619, 8975, 9442, 9799, 8994, 9257, 9544, 9637, 9181, 9545, 8728, 9300, 9972]
  validates_acceptance_of :accepted_terms, on: :create
  validates_presence_of :name, :user, :category, :permalink
  validates_length_of :headline, maximum: 140
  validates_numericality_of :online_days, less_than_or_equal_to: 60, greater_than: 0, if: ->(p){ p.online_days.present? && PROJECTS_WITH_EXTENDED_DEADLINE.exclude?(p.id) }
  validates_numericality_of :online_days, less_than_or_equal_to: 65, greater_than: 0, if: ->(p){ p.online_days.present? && PROJECTS_WITH_EXTENDED_DEADLINE.include?(p.id) }
  validates_numericality_of :goal, greater_than: 9, allow_blank: true
  validates_uniqueness_of :permalink, case_sensitive: false
  validates_format_of :permalink, with: /\A(\w|-)*\Z/

  validates_with StateValidator

  [:between_created_at, :between_expires_at, :between_online_date, :between_updated_at].each do |name|
    define_singleton_method name do |starts_at, ends_at|
      between_dates name.to_s.gsub('between_',''), starts_at, ends_at
    end
  end

  def self.goal_between(starts_at, ends_at)
    where("goal BETWEEN ? AND ?", starts_at, ends_at)
  end

  def self.order_by(sort_field)
    return self.all unless sort_field =~ /^\w+(\.\w+)?\s(desc|asc)$/i
    order(sort_field)
{'fraction_non_alphanumeric': 0.13678039981963025, 'fraction_numerical': 0.011498572072749135, 'mean_word_length': 4.161753297129557, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 80, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2185780', 'n_tokens_mistral': 4649, 'n_tokens_neox': 4389, 'n_words': 905}