af83/chouette-core

View on GitHub
app/models/chouette/line.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module Chouette
  class Line < Chouette::ActiveRecord
    # Must be defined before ObjectidSupport
    before_validation :define_line_referential, on: :create
    before_validation :update_unpermitted_blank_values

    has_metadata
    include LineReferentialSupport
    include ObjectidSupport
    include TransportModeEnumerations
    enumerize_transport_submode

    enumerize :mobility_impaired_accessibility, in: %i(unknown yes no partial), default: :unknown
    enumerize :wheelchair_accessibility, in: %i(unknown yes no partial), default: :unknown
    enumerize :step_free_accessibility, in: %i(unknown yes no partial), default: :unknown
    enumerize :escalator_free_accessibility, in: %i(unknown yes no partial), default: :unknown
    enumerize :lift_free_accessibility, in: %i(unknown yes no partial), default: :unknown
    enumerize :audible_signals_availability, in: %i(unknown yes no partial), default: :unknown
    enumerize :visual_signs_availability, in: %i(unknown yes no partial), default: :unknown

    include ColorSupport
    include CodeSupport
    include ReferentSupport
    include Documentable

    open_color_attribute
    open_color_attribute :text_color

    belongs_to :line_provider, optional: false

    belongs_to :company
    belongs_to :network

    # this 'light' relation prevents the custom fields loading
    belongs_to :company_light, lambda {
                                 select(:id, :name, :line_referential_id, :objectid)
                               }, class_name: 'Chouette::Company', foreign_key: :company_id

    belongs_to_array_in_many :line_routing_constraint_zones, class_name: 'LineRoutingConstraintZone', array_name: :lines
    belongs_to_array_in_many :contracts, class_name: '::Contract', array_name: :lines

    has_array_of :secondary_companies, class_name: 'Chouette::Company'

    has_many :routes
    has_many :journey_patterns, through: :routes
    has_many :vehicle_journeys, through: :journey_patterns
    has_many :routing_constraint_zones, through: :routes
    has_many :time_tables, -> { distinct }, through: :vehicle_journeys

    has_and_belongs_to_many :group_of_lines, class_name: 'Chouette::GroupOfLine', order: 'group_of_lines.name'
    has_and_belongs_to_many :line_notices, class_name: 'Chouette::LineNotice',
                                           join_table: 'public.line_notices_lines'

    has_many :footnotes, inverse_of: :line, validate: true
    accepts_nested_attributes_for :footnotes, reject_if: :all_blank, allow_destroy: true

    attr_reader :group_of_line_tokens

    validates :name, presence: true
    validate :transport_mode_and_submode_match
    validates :registration_number, uniqueness: { scope: :line_provider_id }, allow_blank: true

    scope :by_text, lambda { |text|
                      text.blank? ? all : where('lower(lines.name) LIKE :t or lower(lines.published_name) LIKE :t or lower(lines.objectid) LIKE :t or lower(lines.comment) LIKE :t or lower(lines.number) LIKE :t', t: "%#{text.downcase}%")
                    }

    scope :by_name, lambda { |name|
      joins('LEFT OUTER JOIN public.companies by_name_companies ON by_name_companies.id = lines.company_id')
        .where('
          lines.number LIKE :q
          OR unaccent(lines.name) ILIKE unaccent(:q)
          OR unaccent(by_name_companies.name) ILIKE unaccent(:q)',
               q: "%#{sanitize_sql_like(name)}%")
    }

    scope :for_workbench, lambda { |workbench|
      where(line_referential_id: workbench.line_referential_id)
    }

    scope :notifiable, lambda { |workbench|
      where(id: workbench.notification_rules.pluck(:line_id))
    }

    scope :active, lambda { |*args|
      on_date = args.first || Time.now
      activated.active_from(on_date).active_until(on_date)
    }

    scope :by_provider, ->(line_provider) { where(line_provider_id: line_provider.id) }

    scope :deactivated, -> { where(deactivated: true) }
    scope :activated, -> { where(deactivated: [nil, false]) }
    scope :active_from, ->(from_date) { where('active_from IS NULL OR active_from <= ?', from_date.to_date) }
    scope :active_until, ->(until_date) { where('active_until IS NULL OR active_until >= ?', until_date.to_date) }

    scope :active_after, ->(date) { activated.where('active_until IS NULL OR active_until >= ?', date) }
    scope :active_before, ->(date) { activated.where('active_from IS NULL OR active_from < ?', date) }
    scope :active_between, ->(from, to) { active_after(from).active_before(to) }
    scope :not_active_after, lambda { |date|
                               where('deactivated = ? OR (active_until IS NOT NULL AND active_until < ?)', true, date)
                             }
    scope :not_active_before, lambda { |date|
                                where('deactivated = ? OR (active_from IS NOT NULL AND active_from >= ?)', true, date)
                              }
    scope :not_active_between, lambda { |from, to|
                                 where('deactivated = ? OR (active_from IS NOT NULL AND active_from >= ?) OR (active_until IS NOT NULL AND active_until < ?)', true, to, from)
                               }

    def self.nullable_attributes
      %i[registration_number published_name number comment url color text_color]
    end

    def geometry_presenter
      Chouette::Geometry::LinePresenter.new self
    end

    def commercial_stop_areas
      Chouette::StopArea.joins(children: [stop_points: [route: :line]]).where(lines: { id: id }).distinct
    end

    def stop_areas
      Chouette::StopArea.joins(stop_points: [route: :line]).where(lines: { id: id })
    end

    def stop_areas_last_parents
      Chouette::StopArea.joins(stop_points: [route: :line]).where(lines: { id: id }).collect(&:root).flatten.uniq
    end

    def group_of_line_tokens=(ids)
      self.group_of_line_ids = ids.split(',')
    end

    def vehicle_journey_frequencies?
      vehicle_journeys.unscoped.where(journey_category: 1).count > 0
    end

    def full_display_name
      [get_objectid.short_id, number, name, company_light.try(:name)].compact.join(' - ')
    end

    def display_name
      full_display_name.truncate(70)
    end

    def company_ids
      ([company_id] + Array(secondary_company_ids)).compact
    end

    def companies
      line_referential.companies.where(id: company_ids)
    end

    def active?(on_date = Time.now)
      on_date = on_date.to_date

      return false if deactivated
      return false if active_from && active_from > on_date
      return false if active_until && active_until < on_date

      true
    end

    def always_active_on_period?(from, to)
      return false if deactivated

      return false if active_from && active_from > from
      return false if active_until && active_until < to

      true
    end

    def activated
      !deactivated
    end
    alias activated? activated

    def desactivated
      deactivated
    end

    def desactivated=(value)
      self.deactivated = value
    end

    def activated=(val)
      bool = !ActiveModel::Type::Boolean.new.cast(val)
      self.deactivated = bool
    end

    def status
      activated? ? :activated : :deactivated
    end

    def self.statuses
      %i[activated deactivated]
    end

    def activate
      update deactivated: false
    end

    def deactivate!
      update deactivated: true
    end

    def self.desactivate!
      update_all deactivated: true
    end

    def code
      get_objectid.try(:local_id)
    end

    def same_documentable_workbench?(workbench)
      line_provider.workbench_id == workbench.id
    end

    private

    def define_line_referential
      # TODO: Improve performance ?
      self.line_referential ||= line_provider&.line_referential
    end

    def update_unpermitted_blank_values
      self.transport_submode = :undefined if transport_submode.blank?
    end
  end
end