diff options
101 files changed, 7498 insertions, 18 deletions
| @@ -96,8 +96,12 @@ gem 'squeel'  gem 'enumerize', '~> 0.10.0'  #gem 'ninoxe', '1.2.4' -gem 'ninoxe', git: 'https://github.com/afimb/ninoxe.git' +#gem 'ninoxe', git: 'https://github.com/afimb/ninoxe.git'  #gem 'ninoxe', path: '../ninoxe' +gem 'foreigner', '~> 1.7.4' +gem 'deep_cloneable', '~> 2.0.0' +gem 'acts-as-taggable-on', '>= 3' +  gem 'acts_as_list', '~> 0.6.0'  gem 'acts_as_tree', '~> 2.1.0', require: 'acts_as_tree' @@ -128,6 +132,7 @@ group :development do  end  group :test, :development do +  gem 'awesome_print'    gem 'pry-rails'    gem 'rspec-rails', '~> 3.1.0'    gem 'fakeweb' diff --git a/Gemfile.lock b/Gemfile.lock index 6f6451e35..3b97d82ae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,3 @@ -GIT -  remote: https://github.com/afimb/ninoxe.git -  revision: 98ca11f5ede4307a8f9930a3d2e63d6c2dbb8a27 -  specs: -    ninoxe (1.2.5) -      activerecord (~> 4.1.1) -      activerecord-postgis-adapter -      acts-as-taggable-on (>= 3) -      acts_as_list (>= 0.1.6) -      acts_as_tree (>= 1.1.0) -      deep_cloneable (~> 2.0.0) -      enumerize (~> 0.10.0) -      foreigner (~> 1.7.4) -      georuby (= 2.3.0) -      georuby-ext (= 0.0.5) -  GEM    remote: http://rubygems.org/    remote: http://rails-assets.org/ @@ -69,6 +53,7 @@ GEM      ast (2.0.0)      astrolabe (1.3.0)        parser (>= 2.2.0.pre.3, < 3.0) +    awesome_print (1.6.1)      bcrypt (3.1.10)      bcrypt (3.1.10-java)      better_errors (2.1.1) @@ -484,9 +469,11 @@ DEPENDENCIES    SyslogLogger    activerecord-jdbcpostgresql-adapter (~> 1.3.3)    activerecord-postgis-adapter +  acts-as-taggable-on (>= 3)    acts_as_list (~> 0.6.0)    acts_as_tree (~> 2.1.0)    apartment (~> 1.0.0) +  awesome_print    better_errors    binding_of_caller    breadcrumbs_on_rails @@ -498,6 +485,7 @@ DEPENDENCIES    coffee-rails (~> 4.0.0)    daemons    database_cleaner +  deep_cloneable (~> 2.0.0)    delayed_job_active_record    devise (~> 3.4.0)    devise-async @@ -509,6 +497,7 @@ DEPENDENCIES    fakeweb    faraday_middleware (~> 0.9.1)    font-awesome-sass (~> 4.2.0) +  foreigner (~> 1.7.4)    formtastic (= 2.3.1)    georuby (= 2.3.0)    georuby-ext (= 0.0.5) @@ -527,7 +516,6 @@ DEPENDENCIES    map_layers (= 0.0.4)    mimemagic    newrelic_rpm -  ninoxe!    pg    poltergeist    polylines diff --git a/app/models/chouette/access_link.rb b/app/models/chouette/access_link.rb new file mode 100644 index 000000000..b43dcfb7f --- /dev/null +++ b/app/models/chouette/access_link.rb @@ -0,0 +1,65 @@ +module Chouette +  class AccessLink < TridentActiveRecord +    # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +    self.primary_key = "id" + +    attr_accessor :access_link_type, :link_orientation_type, :link_key + +    belongs_to :access_point, :class_name => 'Chouette::AccessPoint' +    belongs_to :stop_area, :class_name => 'Chouette::StopArea' + +    validates_presence_of :name +    validates_presence_of :link_orientation + +    def self.nullable_attributes +      [:link_distance, :default_duration, :frequent_traveller_duration, :occasional_traveller_duration, +        :mobility_restricted_traveller_duration, :link_type] +    end + +    def access_link_type +      link_type && Chouette::ConnectionLinkType.new(link_type.underscore) +    end + +    def access_link_type=(access_link_type) +      self.link_type = (access_link_type ? access_link_type.camelcase : nil) +    end + +    @@access_link_types = nil +    def self.access_link_types +      @@access_link_types ||= Chouette::ConnectionLinkType.all +    end + +    def link_orientation_type +      link_orientation && Chouette::LinkOrientationType.new(link_orientation.underscore) +    end + +    def link_orientation_type=(link_orientation_type) +      self.link_orientation = (link_orientation_type ? link_orientation_type.camelcase : nil) +    end + +    @@link_orientation_types = nil +    def self.link_orientation_types +      @@link_orientation_types ||= Chouette::LinkOrientationType.all +    end + +    def geometry +      GeoRuby::SimpleFeatures::LineString.from_points( [ access_point.geometry, stop_area.geometry], 4326) if access_point.geometry and stop_area.geometry +    end + +    def link_key +      Chouette::AccessLink.build_link_key(access_point,stop_area,link_orientation_type) +    end +     +    def self.build_link_key(access_point,stop_area,link_orientation_type) +      if link_orientation_type == "access_point_to_stop_area" +        "A_#{access_point.id}-S_#{stop_area.id}" +      else   +        "S_#{stop_area.id}-A_#{access_point.id}" +      end +    end + +    def geometry_presenter +      Chouette::Geometry::AccessLinkPresenter.new self +    end +  end +end diff --git a/app/models/chouette/access_point.rb b/app/models/chouette/access_point.rb new file mode 100644 index 000000000..43c8e1b3a --- /dev/null +++ b/app/models/chouette/access_point.rb @@ -0,0 +1,162 @@ +require 'geokit' +require 'geo_ruby' + +class Chouette::AccessPoint < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" +  include Geokit::Mappable +  has_many :access_links, :dependent => :destroy +  belongs_to :stop_area +   +  attr_accessor :access_point_type +  attr_writer :coordinates +   +  validates_presence_of :name +  validates_presence_of :access_type + +  validates_presence_of :latitude, :if => :longitude +  validates_presence_of :longitude, :if => :latitude +  validates_numericality_of :latitude, :less_than_or_equal_to => 90, :greater_than_or_equal_to => -90, :allow_nil => true +  validates_numericality_of :longitude, :less_than_or_equal_to => 180, :greater_than_or_equal_to => -180, :allow_nil => true + +  validates_format_of :coordinates, :with => %r{\A *-?(0?[0-9](\.[0-9]*)?|[0-8][0-9](\.[0-9]*)?|90(\.[0]*)?) *\, *-?(0?[0-9]?[0-9](\.[0-9]*)?|1[0-7][0-9](\.[0-9]*)?|180(\.[0]*)?) *\Z}, :allow_nil => true, :allow_blank => true + +  def self.nullable_attributes +    [:street_name, :country_code, :comment, :long_lat_type, :zip_code, :city_name] +  end + +  before_save :coordinates_to_lat_lng + +  def combine_lat_lng +    if self.latitude.nil? || self.longitude.nil? +      "" +    else +      self.latitude.to_s+","+self.longitude.to_s  +    end +  end +   +  def coordinates +      @coordinates || combine_lat_lng +  end +   +  def coordinates_to_lat_lng +    if ! @coordinates.nil? +      if @coordinates.empty? +        self.latitude = nil +        self.longitude = nil +      else +        self.latitude = BigDecimal.new(@coordinates.split(",").first) +        self.longitude = BigDecimal.new(@coordinates.split(",").last) +      end +      @coordinates = nil  +    end +  end   + +  def to_lat_lng +    Geokit::LatLng.new(latitude, longitude) if latitude and longitude +  end + +  def geometry +    GeoRuby::SimpleFeatures::Point.from_lon_lat(longitude, latitude, 4326) if latitude and longitude +  end + +  def geometry=(geometry) +    geometry = geometry.to_wgs84 +    self.latitude, self.longitude, self.long_lat_type = geometry.lat, geometry.lng, "WGS84" +  end + +  def position  +    geometry +  end + +  def position=(position) +    position = nil if String === position && position == "" +    position = Geokit::LatLng.normalize(position), 4326 if String === position +    self.latitude = position.lat +    self.longitude = position.lng +  end + +  def default_position  +    stop_area.geometry or stop_area.default_position +  end + + +  def access_point_type +    access_type && Chouette::AccessPointType.new(access_type.underscore) +  end + +  def access_point_type=(access_point_type)    +    self.access_type = (access_point_type ? access_point_type.camelcase : nil) +  end + +  @@access_point_types = nil +  def self.access_point_types +    @@access_point_types ||= Chouette::AccessPointType.all.select do |access_point_type| +      access_point_type.to_i >= 0 +    end +  end + +  def generic_access_link_matrix +     matrix = Array.new +     hash = Hash.new +     access_links.each do |link| +        hash[link.link_key] = link +     end +     key=Chouette::AccessLink.build_link_key(self,stop_area,"access_point_to_stop_area") +     if hash.has_key?(key) +       matrix << hash[key] +     else +       link = Chouette::AccessLink.new +       link.access_point = self +       link.stop_area = stop_area +       link.link_orientation_type = "access_point_to_stop_area" +       matrix << link +     end +     key=Chouette::AccessLink.build_link_key(self,stop_area,"stop_area_to_access_point") +     if hash.has_key?(key) +       matrix << hash[key] +     else +       link = Chouette::AccessLink.new +       link.access_point = self +       link.stop_area = stop_area +       link.link_orientation_type = "stop_area_to_access_point" +       matrix << link +     end +     matrix +  end + +  def detail_access_link_matrix +     matrix = Array.new +     hash = Hash.new +     access_links.each do |link| +        hash[link.link_key] = link +     end +     stop_area.children_at_base.each do |child| +       key=Chouette::AccessLink.build_link_key(self,child,"access_point_to_stop_area") +       if hash.has_key?(key) +         matrix << hash[key] +       else +         link = Chouette::AccessLink.new +         link.access_point = self +         link.stop_area = child +         link.link_orientation_type = "access_point_to_stop_area" +         matrix << link +       end +       key=Chouette::AccessLink.build_link_key(self,child,"stop_area_to_access_point") +       if hash.has_key?(key) +         matrix << hash[key] +       else +         link = Chouette::AccessLink.new +         link.access_point = self +         link.stop_area = child +         link.link_orientation_type = "stop_area_to_access_point" +         matrix << link +       end +     end +     matrix +  end + +  def geometry_presenter +    Chouette::Geometry::AccessPointPresenter.new self +  end +end diff --git a/app/models/chouette/access_point_type.rb b/app/models/chouette/access_point_type.rb new file mode 100644 index 000000000..94d28e5ae --- /dev/null +++ b/app/models/chouette/access_point_type.rb @@ -0,0 +1,50 @@ +class Chouette::AccessPointType < ActiveSupport::StringInquirer + +  def initialize(text_code, numerical_code) +    super text_code.to_s +    @numerical_code = numerical_code +  end + +  def self.new(text_code, numerical_code = nil) +    if text_code and numerical_code +      super +    elsif self === text_code  +      text_code +    else +      if Fixnum === text_code +        text_code, numerical_code = definitions.rassoc(text_code) +      else +        text_code, numerical_code = definitions.assoc(text_code.to_s) +      end + +      super text_code, numerical_code +    end +  end + +  def to_i +    @numerical_code +  end + +  def inspect +    "#{to_s}/#{to_i}" +  end + +  def name +    camelize +  end + +  @@definitions = [ +    ["in", 0], +    ["out", 1], +    ["in_out", 2] +  ] +  cattr_reader :definitions + +  @@all = nil +  def self.all +    @@all ||= definitions.collect do |text_code, numerical_code| +      new(text_code, numerical_code) +    end +  end + +end diff --git a/app/models/chouette/active_record.rb b/app/models/chouette/active_record.rb new file mode 100644 index 000000000..59052075e --- /dev/null +++ b/app/models/chouette/active_record.rb @@ -0,0 +1,66 @@ +#require "active_record" +require 'deep_cloneable' +module Chouette +  class ActiveRecord < ::ActiveRecord::Base + +    self.abstract_class = true + +    before_save :nil_if_blank + +    # to be overrided to set nullable attrs when empty +    def self.nullable_attributes +      [] +    end + +    def nil_if_blank +      self.class.nullable_attributes.each { |attr| self[attr] = nil if self[attr].blank? } +    end + + +    def human_attribute_name(*args) +      self.class.human_attribute_name(*args) +    end + +    # class << self +    #   alias_method :create_reflection_without_chouette_naming, :create_reflection + +    #   def create_reflection(macro, name, options, active_record) +    #     options =  +    #       Reflection.new(macro, name, options, active_record).options_with_default + +    #     create_reflection_without_chouette_naming(macro, name, options, active_record) +    #   end +    # end + + + +    # class Reflection + +    #   attr_reader :macro, :name, :options, :active_record + +    #   def initialize(macro, name, options, active_record) +    #     @macro, @name, @options, @active_record = macro, name.to_s, options, active_record +    #   end + +    #   def collection? +    #     macro == :has_many +    #   end + +    #   def singular_name +    #     collection? ? name.singularize : name +    #   end + +    #   def class_name +    #     "Chouette::#{singular_name.camelize}" +    #   end + +    #   def options_with_default +    #     options.dup.tap do |options| +    #       options[:class_name] ||= class_name +    #     end +    #   end + +    # end + +  end +end diff --git a/app/models/chouette/area_type.rb b/app/models/chouette/area_type.rb new file mode 100644 index 000000000..af614dc55 --- /dev/null +++ b/app/models/chouette/area_type.rb @@ -0,0 +1,56 @@ +class Chouette::AreaType < ActiveSupport::StringInquirer + +  def initialize(text_code, numerical_code) +    super text_code.to_s +    @numerical_code = numerical_code +  end + +  def self.new(text_code, numerical_code = nil) +    if text_code and numerical_code +      super +    elsif self === text_code  +      text_code +    else +      if Fixnum === text_code +        text_code, numerical_code = definitions.rassoc(text_code) +      else +        text_code, numerical_code = definitions.assoc(text_code.to_s) +      end + +      super text_code, numerical_code +    end +  end + +  def to_i +    @numerical_code +  end + +  def inspect +    "#{to_s}/#{to_i}" +  end + +  def name +    if (to_s == 'itl')  +      to_s.upcase +    else +      camelize +    end +  end + +  @@definitions = [ +    ["boarding_position", 0], +    ["quay", 1], +    ["commercial_stop_point", 2], +    ["stop_place", 3], +    ["itl", 4] +  ] +  cattr_reader :definitions + +  @@all = nil +  def self.all +    @@all ||= definitions.collect do |text_code, numerical_code| +      new(text_code, numerical_code) +    end +  end + +end diff --git a/app/models/chouette/command.rb b/app/models/chouette/command.rb new file mode 100644 index 000000000..d2995a000 --- /dev/null +++ b/app/models/chouette/command.rb @@ -0,0 +1,94 @@ +require 'tmpdir' + +#if RUBY_PLATFORM == "java" +  # FIXME disable remove_entry_secure because incompatible with jruby ?!  +  # See http://jira.codehaus.org/browse/JRUBY-4082 +  module FileUtils +    def self.remove_entry_secure(*args) +      self.remove_entry *args +    end +  end +#end + +class Chouette::Command + +  include Chouette::CommandLineSupport + +  @@command = "chouette" +  cattr_accessor :command + +  attr_accessor :database, :schema, :host, :user, :password, :port + +  def initialize(options = {}) +    database_options_from_active_record.merge(options).each do |k,v| +      send "#{k}=", v +    end +  end + +  def database_options_from_active_record +    config = Chouette::ActiveRecord.connection_pool.spec.config +    {  +      :database => config[:database],  +      :user => config[:username], +      :password => config[:password], +      :port => config[:port], +      :host => (config[:host] or "localhost") +    } +  end + + +  def run!(options = {}) +    Dir.mktmpdir do |config_dir| +      chouette_properties = File.join(config_dir, "chouette.properties") +      open(chouette_properties, "w") do |f| +        f.puts "database.name = #{database}" +        f.puts "database.schema = #{schema}" +        #f.puts "database.showsql = true" +        f.puts "hibernate.username = #{user}" +        f.puts "hibernate.password = #{password}" +        f.puts "jdbc.url=jdbc:postgresql://#{host}:#{port}/#{database}" +        f.puts "jdbc.username = #{user}" +        f.puts "jdbc.password = #{password}" +        #f.puts "database.hbm2ddl.auto=update" +      end + +      logger.debug "Chouette properties: #{File.readlines(chouette_properties).collect(&:strip).join(', ')}" + +      command_line = "#{command} -classpath #{config_dir} #{command_options(options)}" +      logger.debug "Execute '#{command_line}'" + +      execute! command_line +    end +  end + +  class Option + +    attr_accessor :key, :value + +    def initialize(key, value) +      @key, @value = key.to_s, value +    end + +    def command_key +      key.camelize(:lower)  +    end + +    def to_s +      unless value == true +        "-#{command_key} #{value}" +      else +        "-#{command_key}" +      end +    end + +  end + +  def command_options(options) +    options.collect do |key, value| +      Option.new(key, value) +    end.sort_by(&:key).join(' ') +  end + +   + +end diff --git a/app/models/chouette/command_line_support.rb b/app/models/chouette/command_line_support.rb new file mode 100644 index 000000000..99c61fa5b --- /dev/null +++ b/app/models/chouette/command_line_support.rb @@ -0,0 +1,35 @@ +module Chouette::CommandLineSupport + +  class ExecutionError < StandardError; end + +  def available_loggers +    [].tap do |logger| +      logger << Chouette::ActiveRecord.logger   +      logger << Rails.logger if defined?(Rails) +      logger << Logger.new($stdout) +    end.compact +  end + +  def logger +    @logger ||= available_loggers.first +  end + +  def max_output_length +    2000 +  end + +  def execute!(command) +    logger.debug "execute '#{command}'" + +    output = `#{command} 2>&1` +    output = "[...] #{output[-max_output_length,max_output_length]}" if output.length > max_output_length +    logger.info output unless output.empty? + +    if $? != 0 +      raise ExecutionError.new("Command failed: #{command} (error code #{$?})") +    end + +    true +  end + +end diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb new file mode 100644 index 000000000..d0375b2e6 --- /dev/null +++ b/app/models/chouette/company.rb @@ -0,0 +1,14 @@ +class Chouette::Company < Chouette::TridentActiveRecord +  has_many :lines + +  validates_format_of :registration_number, :with => %r{\A[0-9A-Za-z_-]+\Z}, :allow_nil => true, :allow_blank => true +  validates_presence_of :name +  validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true + +  def self.nullable_attributes +    [:organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :url, :time_zone] +  end + + +end + diff --git a/app/models/chouette/connection_link.rb b/app/models/chouette/connection_link.rb new file mode 100644 index 000000000..045f7c1d9 --- /dev/null +++ b/app/models/chouette/connection_link.rb @@ -0,0 +1,47 @@ +class Chouette::ConnectionLink < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" + +  attr_accessor :connection_link_type + +  belongs_to :departure, :class_name => 'Chouette::StopArea' +  belongs_to :arrival, :class_name => 'Chouette::StopArea' + +  validates_presence_of :name + +  def self.nullable_attributes +    [:link_distance, :default_duration, :frequent_traveller_duration, :occasional_traveller_duration, +      :mobility_restricted_traveller_duration, :link_type] +  end + +  def connection_link_type +    link_type && Chouette::ConnectionLinkType.new( link_type.underscore) +  end + +  def connection_link_type=(connection_link_type) +    self.link_type = (connection_link_type ? connection_link_type.camelcase : nil) +  end + +  @@connection_link_types = nil +  def self.connection_link_types +    @@connection_link_types ||= Chouette::ConnectionLinkType.all +  end + +  def possible_areas +    Chouette::StopArea.where("area_type != 'ITL'") +  end + +  def stop_areas +    Chouette::StopArea.where(:id => [self.departure_id,self.arrival_id]) +  end + +  def geometry +    GeoRuby::SimpleFeatures::LineString.from_points( [ departure.geometry, arrival.geometry], 4326) if departure.geometry and arrival.geometry +  end + +  def geometry_presenter +    Chouette::Geometry::ConnectionLinkPresenter.new self +  end + +end + diff --git a/app/models/chouette/connection_link_type.rb b/app/models/chouette/connection_link_type.rb new file mode 100644 index 000000000..41635f48c --- /dev/null +++ b/app/models/chouette/connection_link_type.rb @@ -0,0 +1,51 @@ +class Chouette::ConnectionLinkType < ActiveSupport::StringInquirer + +  def initialize(text_code, numerical_code) +    super text_code.to_s +    @numerical_code = numerical_code +  end + +  def self.new(text_code, numerical_code = nil) +    if text_code and numerical_code +      super +    elsif self === text_code  +      text_code +    else +      if Fixnum === text_code +        text_code, numerical_code = definitions.rassoc(text_code) +      else +        text_code, numerical_code = definitions.assoc(text_code.to_s) +      end + +      super text_code, numerical_code +    end +  end + +  def to_i +    @numerical_code +  end + +  def inspect +    "#{to_s}/#{to_i}" +  end + +  def name +    camelize +  end + +  @@definitions = [ +    ["underground", 0], +    ["mixed", 1], +    ["overground", 2] +  ] +  cattr_reader :definitions + +  @@all = nil +  def self.all +    @@all ||= definitions.collect do |text_code, numerical_code| +      new(text_code, numerical_code) +    end +  end + +end + diff --git a/app/models/chouette/direction.rb b/app/models/chouette/direction.rb new file mode 100644 index 000000000..93bc1318b --- /dev/null +++ b/app/models/chouette/direction.rb @@ -0,0 +1,60 @@ +class Chouette::Direction < ActiveSupport::StringInquirer + +  def initialize(text_code, numerical_code) +    super text_code.to_s +    @numerical_code = numerical_code +  end + +  def self.new(text_code, numerical_code = nil) +    if text_code and numerical_code +      super +    elsif self === text_code  +      text_code +    else +      if Fixnum === text_code +        text_code, numerical_code = definitions.rassoc(text_code) +      else +        text_code, numerical_code = definitions.assoc(text_code.to_s) +      end + +      super text_code, numerical_code +    end +  end + +  def to_i +    @numerical_code +  end + +  def inspect +    "#{to_s}/#{to_i}" +  end + +  def name +    to_s +  end + +  @@definitions = [ +    ["straight_forward", 0], +    ["backward", 1], +    ["clock_wise", 2], +    ["counter_clock_wise", 3], +    ["north", 4], +    ["north_west", 5], +    ["west", 6], +    ["south_west", 7], +    ["south", 8], +    ["south_east", 9], +    ["east", 10], +    ["north_east", 11] +  ] +  cattr_reader :definitions + +  @@all = nil +  def self.all +    @@all ||= definitions.collect do |text_code, numerical_code| +      new(text_code, numerical_code) +    end +  end + +end + diff --git a/app/models/chouette/exporter.rb b/app/models/chouette/exporter.rb new file mode 100644 index 000000000..df85a9dde --- /dev/null +++ b/app/models/chouette/exporter.rb @@ -0,0 +1,32 @@ +class Chouette::Exporter + +  attr_reader :schema + +  def initialize(schema) +    @schema = schema +  end + +  def chouette_command +    @chouette_command ||= Chouette::Command.new(:schema => schema) +  end + +  def export(file, options = {}) +    options = { +      :format => :neptune +    }.merge(options) + +    command_options = { +      :c => "export",  +      :o => "line",  +      :format => options.delete(:format).to_s.upcase,  +      :output_file => File.expand_path(file),  +      :optimize_memory => true +    }.merge(options) + +    logger.info "Export #{file} in schema #{schema}" +    chouette_command.run! command_options +  end + +  include Chouette::CommandLineSupport + +end diff --git a/app/models/chouette/file_validator.rb b/app/models/chouette/file_validator.rb new file mode 100644 index 000000000..513648a62 --- /dev/null +++ b/app/models/chouette/file_validator.rb @@ -0,0 +1,47 @@ +class Chouette::FileValidator + +  attr_reader :schema, :database, :user, :password, :host + +  def initialize(schema) +    @schema = schema + +    Chouette::ActiveRecord.connection_pool.spec.config.tap do |config| +      @database = config[:database] +      @user = config[:username] +      @password = config[:password] +      @host = (config[:host] or "localhost") +    end +  end + +  def self.chouette_command=(command) +    Chouette::Command.command = command +  end + +  class << self +    deprecate :chouette_command= => "Use Chouette::Command.command =" +  end + +  def chouette_command +    @chouette_command ||= Chouette::Command.new(:schema => schema) +  end + +  def validate(file, options = {}) +    options = { +      :format => :neptune +    }.merge(options) + +    command_options = { +      :c => "validate",  +      :o => "line",  +      :input_file => File.expand_path(file),  +      :optimize_memory => true +    }.merge(options) + +    logger.info "Validate #{file}" +    chouette_command.run! command_options +  end + + +  include Chouette::CommandLineSupport + +end diff --git a/app/models/chouette/footnote.rb b/app/models/chouette/footnote.rb new file mode 100644 index 000000000..de427b249 --- /dev/null +++ b/app/models/chouette/footnote.rb @@ -0,0 +1,6 @@ +class Chouette::Footnote < Chouette::ActiveRecord +  belongs_to :line, inverse_of: :footnotes +  has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney' + +  validates_presence_of :line +end diff --git a/app/models/chouette/for_alighting_enumerations.rb b/app/models/chouette/for_alighting_enumerations.rb new file mode 100644 index 000000000..4f34927d3 --- /dev/null +++ b/app/models/chouette/for_alighting_enumerations.rb @@ -0,0 +1,8 @@ +module Chouette +  module ForAlightingEnumerations +    extend Enumerize +    extend ActiveModel::Naming +     +    enumerize :for_alighting, in: %w[normal forbidden request_stop is_flexible] +  end +end diff --git a/app/models/chouette/for_boarding_enumerations.rb b/app/models/chouette/for_boarding_enumerations.rb new file mode 100644 index 000000000..48f8762c2 --- /dev/null +++ b/app/models/chouette/for_boarding_enumerations.rb @@ -0,0 +1,8 @@ +module Chouette +  module ForBoardingEnumerations +    extend Enumerize +    extend ActiveModel::Naming + +    enumerize :for_boarding, in: %w[normal forbidden request_stop is_flexible] +  end +end diff --git a/app/models/chouette/group_of_line.rb b/app/models/chouette/group_of_line.rb new file mode 100644 index 000000000..1c1ae5f4c --- /dev/null +++ b/app/models/chouette/group_of_line.rb @@ -0,0 +1,28 @@ +class Chouette::GroupOfLine < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" +   +  has_and_belongs_to_many :lines, :class_name => 'Chouette::Line', :order => 'lines.name' + +  validates_presence_of :name + +  attr_reader :line_tokens +   +  def self.nullable_attributes +    [:comment] +  end +   +  def commercial_stop_areas +    Chouette::StopArea.joins(:children => [:stop_points => [:route => [:line => :group_of_lines] ] ]).where(:group_of_lines => {:id => self.id}).uniq +  end + +  def stop_areas +    Chouette::StopArea.joins(:stop_points => [:route => [:line => :group_of_lines] ]).where(:group_of_lines => {:id => self.id}) +  end +   +  def line_tokens=(ids) +    self.line_ids = ids.split(",") +  end + +end + diff --git a/app/models/chouette/journey_frequency.rb b/app/models/chouette/journey_frequency.rb new file mode 100644 index 000000000..45b8aea8c --- /dev/null +++ b/app/models/chouette/journey_frequency.rb @@ -0,0 +1,36 @@ +module Chouette + +  class JourneyFrequencyValidator < ActiveModel::Validator +    def validate(record) +      timeband = record.timeband +      if timeband +        first_departure_time = record.first_departure_time.utc.strftime( "%H%M%S%N" ) +        last_departure_time  = record.last_departure_time.utc.strftime( "%H%M%S%N" ) +        timeband_start_time  = timeband.start_time.utc.strftime( "%H%M%S%N" ) +        timeband_end_time    = timeband.end_time.utc.strftime( "%H%M%S%N" ) + +        unless first_departure_time.between? timeband_start_time, timeband_end_time +          record.errors[:first_departure_time] << I18n.t('activerecord.errors.models.journey_frequency.start_must_be_after_timeband') +        end +        unless last_departure_time.between? timeband_start_time, timeband_end_time +          record.errors[:last_departure_time] << I18n.t('activerecord.errors.models.journey_frequency.end_must_be_before_timeband') +        end +      end +      if record.first_departure_time == record.last_departure_time +        record.errors[:last_departure_time] << I18n.t('activerecord.errors.models.journey_frequency.end_must_be_different_from_first') +      end +      if record.scheduled_headway_interval.blank? || (record.scheduled_headway_interval.strftime( "%H%M%S%N" ) == Time.current.midnight.strftime( "%H%M%S%N" )) +        record.errors[:scheduled_headway_interval] << I18n.t('activerecord.errors.models.journey_frequency.scheduled_headway_interval_greater_than_zero') +      end +    end +  end + +  class JourneyFrequency < ActiveRecord +    belongs_to :vehicle_journey_frequency, foreign_key: 'vehicle_journey_id' +    belongs_to :timeband +    validates :first_departure_time, presence: true +    validates :last_departure_time,  presence: true +    validates :scheduled_headway_interval, presence: true +    validates_with JourneyFrequencyValidator +  end +end diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb new file mode 100644 index 000000000..d48733edb --- /dev/null +++ b/app/models/chouette/journey_pattern.rb @@ -0,0 +1,109 @@ +class Chouette::JourneyPattern < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" + +  belongs_to :route +  has_many :vehicle_journeys, :dependent => :destroy +  has_many :vehicle_journey_at_stops, :through => :vehicle_journeys +  has_and_belongs_to_many :stop_points, -> { order("stop_points.position") }, :before_add => :vjas_add, :before_remove => :vjas_remove, :after_add => :shortcuts_update_for_add, :after_remove => :shortcuts_update_for_remove +  has_many :journey_pattern_sections +  has_many :route_sections, through: :journey_pattern_sections, dependent: :destroy + +  validates_presence_of :route + +  enum section_status: { todo: 0, completed: 1, control: 2 } + +  attr_accessor  :control_checked +  after_update :control_route_sections, :unless => "control_checked" + +  # TODO: this a workarround +  # otherwise, we loose the first stop_point +  # when creating a new journey_pattern +  def special_update +    bck_sp = self.stop_points.map {|s| s} +    self.update_attributes :stop_points => [] +    self.update_attributes :stop_points => bck_sp +  end + +  def departure_stop_point +    return unless departure_stop_point_id +    Chouette::StopPoint.find( departure_stop_point_id) +  end + +  def arrival_stop_point +    return unless arrival_stop_point_id +    Chouette::StopPoint.find( arrival_stop_point_id) +  end + +  def shortcuts_update_for_add( stop_point) +    stop_points << stop_point unless stop_points.include?( stop_point) + +    ordered_stop_points = stop_points +    ordered_stop_points = ordered_stop_points.sort { |a,b| a.position <=> b.position} unless ordered_stop_points.empty? + +    self.update_attributes( :departure_stop_point_id => (ordered_stop_points.first && ordered_stop_points.first.id), +                             :arrival_stop_point_id => (ordered_stop_points.last && ordered_stop_points.last.id)) +  end + +  def shortcuts_update_for_remove( stop_point) +    stop_points.delete( stop_point) if stop_points.include?( stop_point) + +    ordered_stop_points = stop_points +    ordered_stop_points = ordered_stop_points.sort { |a,b| a.position <=> b.position} unless ordered_stop_points.empty? + +    self.update_attributes( :departure_stop_point_id => (ordered_stop_points.first && ordered_stop_points.first.id), +                             :arrival_stop_point_id => (ordered_stop_points.last && ordered_stop_points.last.id)) +  end + +  def vjas_add( stop_point) +    return if new_record? + +    vehicle_journeys.each do |vj| +      vjas = vj.vehicle_journey_at_stops.create :stop_point_id => stop_point.id +    end +  end + +  def vjas_remove( stop_point) +    return if new_record? + +    vehicle_journey_at_stops.where( :stop_point_id => stop_point.id).each do |vjas| +      vjas.destroy +    end +  end + +  def control_route_sections +    stop_area_ids = self.stop_points.map(&:stop_area_id) +    control_route_sections_by_stop_areas(stop_area_ids) +  end + +  def control_route_sections_by_stop_areas(stop_area_ids) +    journey_pattern_section_all +    i = 0 +    to_control = false +    stop_area_ids.each_cons(2) do |a| +      jps = @route_sections_orders[i] +      i += 1 +      unless jps +        to_control = true +        next +      end +      unless [jps.route_section.departure.id, jps.route_section.arrival.id] == a +        jps.destroy +        to_control = true +      end +    end +    self.control_checked = true +    to_control ? self.control! : self.completed! +  end + +  protected + +  def journey_pattern_section_all +    @route_sections_orders = {} +    self.journey_pattern_sections.all.map do |journey_pattern_section| +      @route_sections_orders[journey_pattern_section.rank] = journey_pattern_section +    end +  end + +end + diff --git a/app/models/chouette/journey_pattern_section.rb b/app/models/chouette/journey_pattern_section.rb new file mode 100644 index 000000000..3ccba8ec0 --- /dev/null +++ b/app/models/chouette/journey_pattern_section.rb @@ -0,0 +1,20 @@ +class Chouette::JourneyPatternSection < Chouette::ActiveRecord +  belongs_to :journey_pattern +  belongs_to :route_section + +  validates :journey_pattern_id, presence: true +  validates :route_section_id, presence: true +  validates :rank, presence: true, numericality: :only_integer +  validates :journey_pattern_id, uniqueness: { scope: [:route_section_id, :rank] } + +  default_scope { order(:rank) } + +  def self.update_by_journey_pattern_rank(journey_pattern_id, route_section_id, rank) +    jps = self.find_or_initialize_by(journey_pattern_id: journey_pattern_id, rank: rank) +    if route_section_id.present? +      jps.update_attributes(route_section_id: route_section_id) +    else +      jps.destroy +    end +  end +end diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb new file mode 100644 index 000000000..d69203233 --- /dev/null +++ b/app/models/chouette/line.rb @@ -0,0 +1,74 @@ +class Chouette::Line < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" + +  belongs_to :company +  belongs_to :network +  has_many :routes, :dependent => :destroy +  has_many :journey_patterns, :through => :routes +  has_many :vehicle_journeys, :through => :journey_patterns + +  has_and_belongs_to_many :group_of_lines, :class_name => 'Chouette::GroupOfLine', :order => 'group_of_lines.name' + +  has_many :footnotes, :inverse_of => :line, :validate => :true, :dependent => :destroy +  accepts_nested_attributes_for :footnotes, :reject_if => :all_blank, :allow_destroy => true + +  attr_reader :group_of_line_tokens +  attr_accessor :transport_mode + +  validates_presence_of :network +  validates_presence_of :company + +  validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true +  validates_format_of :stable_id, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true +  validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true +  validates_format_of :color, :with => %r{\A[0-9a-fA-F]{6}\Z}, :allow_nil => true, :allow_blank => true +  validates_format_of :text_color, :with => %r{\A[0-9a-fA-F]{6}\Z}, :allow_nil => true, :allow_blank => true + +  validates_presence_of :name + +  def self.nullable_attributes +    [:published_name, :number, :comment, :url, :color, :text_color, :stable_id] +  end + +  def geometry_presenter +    Chouette::Geometry::LinePresenter.new self +  end + +  def transport_mode +    # return nil if transport_mode_name is nil +    transport_mode_name && Chouette::TransportMode.new( transport_mode_name.underscore) +  end + +  def transport_mode=(transport_mode) +    self.transport_mode_name = (transport_mode ? transport_mode.camelcase : nil) +  end + +  @@transport_modes = nil +  def self.transport_modes +    @@transport_modes ||= Chouette::TransportMode.all.select do |transport_mode| +      transport_mode.to_i > 0 +    end +  end + +  def commercial_stop_areas +    Chouette::StopArea.joins(:children => [:stop_points => [:route => :line] ]).where(:lines => {:id => self.id}).uniq +  end + +  def stop_areas +    Chouette::StopArea.joins(:stop_points => [:route => :line]).where(:lines => {:id => self.id}) +  end + +  def stop_areas_last_parents +    Chouette::StopArea.joins(:stop_points => [:route => :line]).where(:lines => {:id => self.id}).collect(&:root).flatten.uniq +  end + +  def group_of_line_tokens=(ids) +    self.group_of_line_ids = ids.split(",") +  end + +  def vehicle_journey_frequencies? +    self.vehicle_journeys.unscoped.where(journey_category: 1).count > 0 +  end + +end diff --git a/app/models/chouette/link_orientation_type.rb b/app/models/chouette/link_orientation_type.rb new file mode 100644 index 000000000..ec279aba3 --- /dev/null +++ b/app/models/chouette/link_orientation_type.rb @@ -0,0 +1,49 @@ +class Chouette::LinkOrientationType < ActiveSupport::StringInquirer + +  def initialize(text_code, numerical_code) +    super text_code.to_s +    @numerical_code = numerical_code +  end + +  def self.new(text_code, numerical_code = nil) +    if text_code and numerical_code +      super +    elsif self === text_code  +      text_code +    else +      if Fixnum === text_code +        text_code, numerical_code = definitions.rassoc(text_code) +      else +        text_code, numerical_code = definitions.assoc(text_code.to_s) +      end + +      super text_code, numerical_code +    end +  end + +  def to_i +    @numerical_code +  end + +  def inspect +    "#{to_s}/#{to_i}" +  end + +  def name +    camelize +  end + +  @@definitions = [ +    ["access_point_to_stop_area", 0], +    ["stop_area_to_access_point", 1] +  ] +  cattr_reader :definitions + +  @@all = nil +  def self.all +    @@all ||= definitions.collect do |text_code, numerical_code| +      new(text_code, numerical_code) +    end +  end + +end diff --git a/app/models/chouette/loader.rb b/app/models/chouette/loader.rb new file mode 100644 index 000000000..40a2be4ee --- /dev/null +++ b/app/models/chouette/loader.rb @@ -0,0 +1,110 @@ +class Chouette::Loader + +  attr_reader :schema, :database, :user, :password, :host + +  def initialize(schema) +    @schema = schema + +    Chouette::ActiveRecord.connection_pool.spec.config.tap do |config| +      @database = config[:database] +      @user = config[:username] +      @password = config[:password] +      @host = (config[:host] or "localhost") +    end +  end + +  # Load dump where datas are in schema 'chouette' +  def load_dump(file) +    logger.info "Load #{file} in schema #{schema}" +    with_pg_password do +      execute!("sed -e 's/ chouette/ \"#{schema}\"/' -e 's/ OWNER TO .*;/ OWNER TO #{user};/' #{file} | psql #{pg_options} --set ON_ERROR_ROLLBACK=1 --set ON_ERROR_STOP=1") +    end +    self +  end + +  def self.chouette_command=(command) +    Chouette::Command.command = command +  end + +  class << self +    deprecate :chouette_command= => "Use Chouette::Command.command =" +  end + +  def chouette_command +    @chouette_command ||= Chouette::Command.new(:schema => schema) +  end + +  def import(file, options = {}) +    options = { +      :format => :neptune +    }.merge(options) + +    command_options = { +      :c => "import",  +      :o => "line",  +      :format => options.delete(:format).to_s.upcase,  +      :input_file => File.expand_path(file),  +      :optimize_memory => true +    }.merge(options) + +    logger.info "Import #{file} in schema #{schema}" +    chouette_command.run! command_options +  end + +  def backup(file) +    logger.info "Backup schema #{schema} in #{file}" + +    with_pg_password do +      execute!("pg_dump -n #{schema} -f #{file} #{pg_options}") +    end + +    self +  end + +  def pg_options +    [].tap do |options| +      options << "-U #{user}" if user +      options << "-h #{host}" if host +      options << database +    end.join(" ") +  end + +  def create +    logger.info "Create schema #{schema}" +    with_pg_password do +      execute!("psql -c 'CREATE SCHEMA \"#{schema}\";' #{pg_options}") +    end +    self +  end + +  def drop +    logger.info "Drop schema #{schema}" +    with_pg_password do +      execute!("psql -c 'DROP SCHEMA \"#{schema}\" CASCADE;' #{pg_options}") +    end +    self +  end + +  def with_pg_password(&block) +    ENV['PGPASSWORD'] = password.to_s if password +    begin +      yield +    ensure +      ENV['PGPASSWORD'] = nil +    end +  end + +  @@binarisation_command = "binarisation" +  cattr_accessor :binarisation_command + +  def binarize(period, target_directory) +    # TODO check these computed daybefore/dayafter +    day_before = Date.today - period.begin +    day_after = period.end - period.begin + +    execute! "#{binarisation_command} --host=#{host} --dbname=#{database} --user=#{user} --password=#{password} --schema=#{schema} --daybefore=#{day_before} --dayafter=#{day_after} --targetdirectory=#{target_directory}" +  end + +  include Chouette::CommandLineSupport + +end diff --git a/app/models/chouette/network.rb b/app/models/chouette/network.rb new file mode 100644 index 000000000..a631d70ec --- /dev/null +++ b/app/models/chouette/network.rb @@ -0,0 +1,46 @@ +class Chouette::Network < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" + +  has_many :lines + +  attr_accessor :source_type_name + +  validates_format_of :registration_number, :with => %r{\A[0-9A-Za-z_-]+\Z}, :allow_nil => true, :allow_blank => true +  validates_presence_of :name + +  def self.object_id_key +    "PTNetwork" +  end + +  def self.nullable_attributes +    [:source_name, :source_type, :source_identifier, :comment] +  end + +  def commercial_stop_areas +    Chouette::StopArea.joins(:children => [:stop_points => [:route => [:line => :network] ] ]).where(:networks => {:id => self.id}).uniq +  end + +  def stop_areas +    Chouette::StopArea.joins(:stop_points => [:route => [:line => :network] ]).where(:networks => {:id => self.id}) +  end + +  def source_type_name +    # return nil if source_type is nil +    source_type && Chouette::SourceType.new( source_type.underscore) +  end + +  def source_type_name=(source_type_name) +    self.source_type = (source_type_name ? source_type_name.camelcase : nil) +  end + +  @@source_type_names = nil +  def self.source_type_names +    @@source_type_names ||= Chouette::SourceType.all.select do |source_type_name| +      source_type_name.to_i > 0 +    end +  end + + +end + diff --git a/app/models/chouette/object_id.rb b/app/models/chouette/object_id.rb new file mode 100644 index 000000000..4f58048e1 --- /dev/null +++ b/app/models/chouette/object_id.rb @@ -0,0 +1,36 @@ +class Chouette::ObjectId < String + +  def valid? +    parts.present? +  end +  alias_method :objectid?, :valid? + +  @@format = /^([0-9A-Za-z_]+):([A-Za-z]+):([0-9A-Za-z_-]+)$/  +  cattr_reader :format + +  def parts +    match(format).try(:captures) +  end + +  def system_id +    parts.try(:first) +  end + +  def object_type +    parts.try(:second) +  end + +  def local_id +    parts.try(:third) +  end +   +  def self.create(system_id, object_type, local_id) +    new [system_id, object_type, local_id].join(":") +  end + +  def self.new(string) +    string ||= "" +    self === string ? string : super +  end + +end diff --git a/app/models/chouette/pt_link.rb b/app/models/chouette/pt_link.rb new file mode 100644 index 000000000..8a4e368ea --- /dev/null +++ b/app/models/chouette/pt_link.rb @@ -0,0 +1,37 @@ +require 'geokit' + +class Chouette::PtLink < Chouette::ActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" + +  include Geokit::Mappable + +  def geometry +    the_geom +  end + +  def self.import_csv +    csv_file = Rails.root + "chouette_pt_links.csv" +    if File.exists?( csv_file) +      csv = CSV::Reader.parse(File.read(csv_file)) + +      slug = csv.shift.first + +      Network::Base.find_by_slug( slug).tune_connection + +      csv.each do |row| +        origin = Chouette::StopArea.find_by_objectid( row[0]) +        destination = Chouette::StopArea.find_by_objectid( row[1]) + +        raise "unknown origin #{row[0]}" unless origin +        raise "unknown destination #{row[1]}" unless destination + +        Chouette::PtLink.create( :departure_id => origin.id, +                                 :arrival_id => destination.id, +                                 :the_geom => GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb( row[2])) +      end +    end +     +  end + +end diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb new file mode 100644 index 000000000..d5e39ac12 --- /dev/null +++ b/app/models/chouette/route.rb @@ -0,0 +1,183 @@ +class Chouette::Route < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" + +  attr_accessor :wayback_code +  attr_accessor :direction_code + +  def self.nullable_attributes +    [:published_name, :comment, :number, :name] +  end + +  belongs_to :line + +  has_many :journey_patterns, :dependent => :destroy +  has_many :vehicle_journeys, :dependent => :destroy do +    def timeless +      Chouette::Route.vehicle_journeys_timeless(proxy_association.owner.journey_patterns.pluck( :departure_stop_point_id)) +    end +  end +  has_many :vehicle_journey_frequencies, :dependent => :destroy do +    # Todo : I think there is a better way to do this. +    def timeless +      Chouette::Route.vehicle_journeys_timeless(proxy_association.owner.journey_patterns.pluck( :departure_stop_point_id)) +    end +  end +  belongs_to :opposite_route, :class_name => 'Chouette::Route', :foreign_key => :opposite_route_id +  has_many :stop_points, -> { order('position ASC') }, :dependent => :destroy do +    def find_by_stop_area(stop_area) +      stop_area_ids = Integer === stop_area ? [stop_area] : (stop_area.children_in_depth + [stop_area]).map(&:id) +      where( :stop_area_id => stop_area_ids).first or +        raise ActiveRecord::RecordNotFound.new("Can't find a StopArea #{stop_area.inspect} in Route #{proxy_owner.id.inspect}'s StopPoints") +    end + +    def between(departure, arrival) +      between_positions = [departure, arrival].collect do |endpoint| +        case endpoint +        when Chouette::StopArea +          find_by_stop_area(endpoint).position +        when  Chouette::StopPoint +          endpoint.position +        when Integer +          endpoint +        else +          raise ActiveRecord::RecordNotFound.new("Can't determine position in route #{proxy_owner.id} with #{departure.inspect}") +        end +      end +      where(" position between ? and ? ", between_positions.first, between_positions.last) +    end +  end +  has_many :stop_areas, -> { order('stop_points.position ASC') }, :through => :stop_points do +    def between(departure, arrival) +      departure, arrival = [departure, arrival].collect do |endpoint| +        String === endpoint ? Chouette::StopArea.find_by_objectid(endpoint) : endpoint +      end +      proxy_owner.stop_points.between(departure, arrival).includes(:stop_area).collect(&:stop_area) +    end +  end +  accepts_nested_attributes_for :stop_points, :allow_destroy => :true + +  # validates_presence_of :name +  validates_presence_of :line +  # validates_presence_of :direction_code +  # validates_presence_of :wayback_code + +  before_destroy :dereference_opposite_route + +  after_commit :journey_patterns_control_route_sections + +  def geometry_presenter +    Chouette::Geometry::RoutePresenter.new self +  end + +  def dereference_opposite_route +    self.line.routes.each do |r| +      r.update_attributes( :opposite_route => nil) if r.opposite_route == self +    end +  end + +  def geometry +    points = stop_areas.map(&:to_lat_lng).compact.map do |loc| +      [loc.lng, loc.lat] +    end +    GeoRuby::SimpleFeatures::LineString.from_coordinates( points, 4326) +  end + +  def time_tables +    self.vehicle_journeys.joins(:time_tables).map(&:"time_tables").flatten.uniq +  end + +  def sorted_vehicle_journeys(journey_category_model) +    send(journey_category_model) +        .joins(:journey_pattern, :vehicle_journey_at_stops) +        .where("vehicle_journey_at_stops.stop_point_id=journey_patterns.departure_stop_point_id") +        .order( "vehicle_journey_at_stops.departure_time") +  end + +  def self.direction_binding +    { "A" => "straight_forward", +      "R" => "backward", +      "ClockWise" => "clock_wise", +      "CounterClockWise" => "counter_clock_wise", +      "North" => "north", +      "NorthWest" => "north_west", +      "West" => "west", +      "SouthWest" => "south_west", +      "South" => "south", +      "SouthEast" => "south_east", +      "East" => "east", +      "NorthEast" => "north_east"} +  end +  def direction_code +    return nil if self.class.direction_binding[direction].nil? +    Chouette::Direction.new( self.class.direction_binding[direction]) +  end +  def direction_code=(direction_code) +    self.direction = nil +    self.class.direction_binding.each do |k,v| +      self.direction = k if v==direction_code +    end +  end +  @@directions = nil +  def self.directions +    @@directions ||= Chouette::Direction.all +  end +  def self.wayback_binding +    { "A" => "straight_forward", "R" => "backward"} +  end +  def wayback_code +    return nil if self.class.wayback_binding[wayback].nil? +    Chouette::Wayback.new( self.class.wayback_binding[wayback]) +  end +  def wayback_code=(wayback_code) +    self.wayback = nil +    self.class.wayback_binding.each do |k,v| +      self.wayback = k if v==wayback_code +    end +  end +  @@waybacks = nil +  def self.waybacks +    @@waybacks ||= Chouette::Wayback.all +  end + +  def stop_point_permutation?( stop_point_ids) +    stop_points.map(&:id).map(&:to_s).sort == stop_point_ids.map(&:to_s).sort +  end + +  def reorder!( stop_point_ids) +    return false unless stop_point_permutation?( stop_point_ids) + +    stop_area_id_by_stop_point_id = {} +    stop_points.each do |sp| +      stop_area_id_by_stop_point_id.merge!( sp.id => sp.stop_area_id) +    end + +    reordered_stop_area_ids = [] +    stop_point_ids.each do |stop_point_id| +      reordered_stop_area_ids << stop_area_id_by_stop_point_id[ stop_point_id.to_i] +    end + +    stop_points.each_with_index do |sp, index| +      if sp.stop_area_id.to_s != reordered_stop_area_ids[ index].to_s +        #result = sp.update_attributes( :stop_area_id => reordered_stop_area_ids[ index]) +        sp.stop_area_id = reordered_stop_area_ids[ index] +        result = sp.save! +      end +    end + +    return true +  end + +  def journey_patterns_control_route_sections +    self.journey_patterns.each do |jp| +      jp.control_route_sections +    end +  end + +  protected + +  def self.vehicle_journeys_timeless(stop_point_id) +    all( :conditions => ['vehicle_journeys.id NOT IN (?)', Chouette::VehicleJourneyAtStop.where(stop_point_id: stop_point_id).pluck(:vehicle_journey_id)] ) +  end + +end diff --git a/app/models/chouette/route_section.rb b/app/models/chouette/route_section.rb new file mode 100644 index 000000000..99f1e776f --- /dev/null +++ b/app/models/chouette/route_section.rb @@ -0,0 +1,82 @@ +class Chouette::RouteSection < Chouette::TridentActiveRecord +  belongs_to :departure, class_name: 'Chouette::StopArea' +  belongs_to :arrival, class_name: 'Chouette::StopArea' +  has_many :journey_pattern_sections +  has_many :journey_patterns, through: :journey_pattern_sections, dependent: :destroy + +  validates :departure, :arrival, presence: true +  validates :processed_geometry, presence: true + +  scope :by_endpoint_name, ->(endpoint, name) do +    joins("INNER JOIN stop_areas #{endpoint} ON #{endpoint}.id = route_sections.#{endpoint}_id").where(["#{endpoint}.name ilike ?", "%#{name}%"]) +  end +  scope :by_line_id, ->(line_id) do +    joins(:journey_pattern_sections, :journey_patterns).joins('INNER JOIN routes ON journey_patterns.route_id = routes.id').where("routes.line_id = #{line_id}") +  end + +  def stop_areas +    [departure, arrival].compact +  end + +  def default_geometry +    points = stop_areas.collect(&:geometry).compact +    GeoRuby::SimpleFeatures::LineString.from_points(points) if points.many? +  end + +  def name +    stop_areas.map do |stop_area| +      stop_area.try(:name) or '?' +    end.join(' - ') + " (#{geometry_description})" +  end + +  def via_count +    input_geometry ? [ input_geometry.points.count - 2, 0 ].max : 0 +  end + +  def geometry_description +    if input_geometry || processed_geometry +      [ "#{distance.to_i}m" ].tap do |parts| +        parts << "#{via_count} #{'via'.pluralize(via_count)}" if via_count > 0 +      end.join(' - ') +    else +      "-" +    end +  end + +  DEFAULT_PROCESSOR = Proc.new { |section| section.input_geometry || section.default_geometry.try(:to_rgeo) } + +  @@processor = DEFAULT_PROCESSOR +  cattr_accessor :processor + +  def instance_processor +    no_processing || processor.nil? ? DEFAULT_PROCESSOR : processor +  end + +  def process_geometry +    if input_geometry_changed? || processed_geometry.nil? +      self.processed_geometry = instance_processor.call(self) +      self.distance = processed_geometry.to_georuby.to_wgs84.spherical_distance if processed_geometry +    end + +    true +  end +  before_validation :process_geometry + +  def editable_geometry=(geometry) +    self.input_geometry = geometry +  end + +  def editable_geometry +    input_geometry.try(:to_georuby) or default_geometry +  end + +  def editable_geometry_before_type_cast +    editable_geometry.to_ewkt +  end + +  def geometry(mode = nil) +    mode ||= :processed +    mode == :editable ? editable_geometry : processed_geometry.to_georuby +  end + +end diff --git a/app/models/chouette/source_type.rb b/app/models/chouette/source_type.rb new file mode 100644 index 000000000..124a6c433 --- /dev/null +++ b/app/models/chouette/source_type.rb @@ -0,0 +1,56 @@ +class Chouette::SourceType < ActiveSupport::StringInquirer + +  def initialize(text_code, numerical_code) +    super text_code.to_s +    @numerical_code = numerical_code +  end + +  def self.new(text_code, numerical_code = nil) +    if text_code and numerical_code +      super +    elsif self === text_code  +      text_code +    else +      if Fixnum === text_code +        text_code, numerical_code = definitions.rassoc(text_code) +      else +        text_code, numerical_code = definitions.assoc(text_code.to_s) +      end + +      super text_code, numerical_code +    end +  end + +  def to_i +    @numerical_code +  end + +  def inspect +    "#{to_s}/#{to_i}" +  end + +  def name +    camelize +  end + +  @@definitions = [ +    ["public_and_private_utilities", 0], +    ["road_authorities", 1], +    ["transit_operator", 2], +    ["public_transport", 3], +    ["passenger_transport_coordinating_authority", 4], +    ["travel_information_service_provider", 5], +    ["travel_agency", 6], +    ["individual_subject_of_travel_itinerary", 7], +    ["other_information", 8] +  ] +  cattr_reader :definitions + +  @@all = nil +  def self.all +    @@all ||= definitions.collect do |text_code, numerical_code| +      new(text_code, numerical_code) +    end +  end + +end diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb new file mode 100644 index 000000000..b7cdd313a --- /dev/null +++ b/app/models/chouette/stop_area.rb @@ -0,0 +1,323 @@ +require 'geokit' +require 'geo_ruby' + +class Chouette::StopArea < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" +  include Geokit::Mappable +  has_many :stop_points, :dependent => :destroy +  has_many :access_points, :dependent => :destroy +  has_many :access_links, :dependent => :destroy +  has_and_belongs_to_many :routing_lines, :class_name => 'Chouette::Line', :foreign_key => "stop_area_id", :association_foreign_key => "line_id", :join_table => "routing_constraints_lines", :order => "lines.number" +  has_and_belongs_to_many :routing_stops, :class_name => 'Chouette::StopArea', :foreign_key => "parent_id", :association_foreign_key => "child_id", :join_table => "stop_areas_stop_areas", :order => "stop_areas.name" + +  acts_as_tree :foreign_key => 'parent_id',:order => "name" + +  attr_accessor :stop_area_type +  attr_accessor :children_ids +  attr_writer :coordinates + +  after_update :journey_patterns_control_route_sections, +               if: Proc.new { |stop_area| ['boarding_position', 'quay'].include? stop_area.stop_area_type } + +  validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_blank => true +  validates_presence_of :name +  validates_presence_of :area_type + +  validates_presence_of :latitude, :if => :longitude +  validates_presence_of :longitude, :if => :latitude +  validates_numericality_of :latitude, :less_than_or_equal_to => 90, :greater_than_or_equal_to => -90, :allow_nil => true +  validates_numericality_of :longitude, :less_than_or_equal_to => 180, :greater_than_or_equal_to => -180, :allow_nil => true + +  validates_format_of :coordinates, :with => %r{\A *-?(0?[0-9](\.[0-9]*)?|[0-8][0-9](\.[0-9]*)?|90(\.[0]*)?) *\, *-?(0?[0-9]?[0-9](\.[0-9]*)?|1[0-7][0-9](\.[0-9]*)?|180(\.[0]*)?) *\Z}, :allow_nil => true, :allow_blank => true +  validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true + +  def self.nullable_attributes +    [:registration_number, :street_name, :country_code, :fare_code, +     :nearest_topic_name, :comment, :long_lat_type, :zip_code, :city_name, :url, :time_zone] +  end + +  after_update :clean_invalid_access_links + +  before_save :coordinates_to_lat_lng + +  def combine_lat_lng +    if self.latitude.nil? || self.longitude.nil? +      "" +    else +      self.latitude.to_s+","+self.longitude.to_s +    end +  end + +  def coordinates +      @coordinates || combine_lat_lng +  end + +  def coordinates_to_lat_lng +    if ! @coordinates.nil? +      if @coordinates.empty? +        self.latitude = nil +        self.longitude = nil +      else +        self.latitude = BigDecimal.new(@coordinates.split(",").first) +        self.longitude = BigDecimal.new(@coordinates.split(",").last) +      end +      @coordinates = nil +    end +  end + +  def children_in_depth +    return [] if self.children.empty? + +    self.children + self.children.map do |child| +      child.children_in_depth +    end.flatten.compact +  end + +  def possible_children +    case area_type +      when "BoardingPosition" then [] +      when "Quay" then [] +      when "CommercialStopPoint" then Chouette::StopArea.where(:area_type => ['Quay', 'BoardingPosition']) - [self] +      when "StopPlace" then Chouette::StopArea.where(:area_type => ['StopPlace', 'CommercialStopPoint']) - [self] +      when "ITL" then Chouette::StopArea.where(:area_type => ['Quay', 'BoardingPosition', 'StopPlace', 'CommercialStopPoint']) +    end + +  end + +  def possible_parents +    case area_type +      when "BoardingPosition" then Chouette::StopArea.where(:area_type => "CommercialStopPoint")  - [self] +      when "Quay" then Chouette::StopArea.where(:area_type => "CommercialStopPoint") - [self] +      when "CommercialStopPoint" then Chouette::StopArea.where(:area_type => "StopPlace") - [self] +      when "StopPlace" then Chouette::StopArea.where(:area_type => "StopPlace") - [self] +    end +  end + +  def geometry_presenter +    Chouette::Geometry::StopAreaPresenter.new self +  end + +  def lines +    if (area_type == 'CommercialStopPoint') +      self.children.collect(&:stop_points).flatten.collect(&:route).flatten.collect(&:line).flatten.uniq +    else +      self.stop_points.collect(&:route).flatten.collect(&:line).flatten.uniq +    end +  end + +  def routes +    self.stop_points.collect(&:route).flatten.uniq +  end + +  def self.commercial +    where :area_type => "CommercialStopPoint" +  end + +  def self.stop_place +    where :area_type => "StopPlace" +  end + +  def self.physical +    where :area_type => [ "BoardingPosition", "Quay" ] +  end + +  def self.itl +    where :area_type => "ITL" +  end + +  def to_lat_lng +    Geokit::LatLng.new(latitude, longitude) if latitude and longitude +  end + +  def geometry +    GeoRuby::SimpleFeatures::Point.from_lon_lat(longitude, latitude, 4326) if latitude and longitude +  end + +  def geometry=(geometry) +    geometry = geometry.to_wgs84 +    self.latitude, self.longitude, self.long_lat_type = geometry.lat, geometry.lng, "WGS84" +  end + +  def position +    geometry +  end + +  def position=(position) +    position = nil if String === position && position == "" +    position = Geokit::LatLng.normalize(position), 4326 if String === position +    self.latitude = position.lat +    self.longitude = position.lng +  end + +  def default_position +    # for first StopArea ... the bounds is nil :( +    Chouette::StopArea.bounds and Chouette::StopArea.bounds.center +  end + +  def self.near(origin, distance = 0.3) +    origin = origin.to_lat_lng + +    lat_degree_units = units_per_latitude_degree(:kms) +    lng_degree_units = units_per_longitude_degree(origin.lat, :kms) + +    where "SQRT(POW(#{lat_degree_units}*(#{origin.lat}-latitude),2)+POW(#{lng_degree_units}*(#{origin.lng}-longitude),2)) <= #{distance}" +  end + +  def self.bounds +    # Give something like : +    # [["113.5292500000000000", "22.1127580000000000", "113.5819330000000000", "22.2157050000000000"]] +    min_and_max = connection.select_rows("select min(longitude) as min_lon, min(latitude) as min_lat, max(longitude) as max_lon, max(latitude) as max_lat from #{table_name} where latitude is not null and longitude is not null").first +    return nil unless min_and_max + +    # Ignore [nil, nil, nil, nil] +    min_and_max.compact! +    return nil unless min_and_max.size == 4 + +    min_and_max.collect! { |n| n.to_f } + +    # We need something like : +    # [[113.5292500000000000, 22.1127580000000000], [113.5819330000000000, 22.2157050000000000]] +    coordinates = min_and_max.each_slice(2).to_a +    GeoRuby::SimpleFeatures::Envelope.from_coordinates coordinates +  end + +  def stop_area_type +    area_type && Chouette::AreaType.new(area_type.underscore) +  end + +  def stop_area_type=(stop_area_type) +    self.area_type = (stop_area_type ? stop_area_type.camelcase : nil) +    if self.area_type == 'Itl' +      self.area_type = 'ITL' +    end +  end + +  @@stop_area_types = nil +  def self.stop_area_types +    @@stop_area_types ||= Chouette::AreaType.all.select do |stop_area_type| +      stop_area_type.to_i >= 0 +    end +  end + +  def children_ids=(children_ids) +    children = children_ids.split(',').uniq +    # remove unset children +    self.children.each do |child| +      if (! children.include? child.id) +        child.update_attribute :parent_id, nil +      end +    end +    # add new children +    Chouette::StopArea.find(children).each do |child| +       child.update_attribute :parent_id, self.id +    end +  end + +  def routing_stop_ids=(routing_stop_ids) +    stops = routing_stop_ids.split(',').uniq +    self.routing_stops.clear +    Chouette::StopArea.find(stops).each do |stop| +      self.routing_stops << stop +    end +  end + +  def routing_line_ids=(routing_line_ids) +    lines = routing_line_ids.split(',').uniq +    self.routing_lines.clear +    Chouette::Line.find(lines).each do |line| +      self.routing_lines << line +    end +  end + +  def self.without_geometry +    where("latitude is null or longitude is null") +  end + +  def self.with_geometry +    where("latitude is not null and longitude is not null") +  end + +  def self.default_geometry! +    count = 0 +    where(nil).find_each do |stop_area| +      Chouette::StopArea.unscoped do +        count += 1 if stop_area.default_geometry! +      end +    end +    count +  end + +  def default_geometry! +    new_geometry = default_geometry +    update_attribute :geometry, new_geometry if new_geometry +  end + +  def default_geometry +    children_geometries = children.with_geometry.map(&:geometry).uniq +    GeoRuby::SimpleFeatures::Point.centroid children_geometries if children_geometries.present? +  end + +  def generic_access_link_matrix +     matrix = Array.new +     access_points.each do |access_point| +       matrix += access_point.generic_access_link_matrix +     end +     matrix +  end + +  def detail_access_link_matrix +     matrix = Array.new +     access_points.each do |access_point| +       matrix += access_point.detail_access_link_matrix +     end +     matrix +  end + +  def children_at_base +    list = Array.new +    children_in_depth.each do |child| +      if child.area_type == 'Quay' || child.area_type == 'BoardingPosition' +        list << child +      end +    end +    list +  end + +  def parents +    list = Array.new +    if !parent.nil? +      list << parent +      list += parent.parents +    end +    list +  end + +  def clean_invalid_access_links +    stop_parents = parents +    access_links.each do |link| +      unless stop_parents.include? link.access_point.stop_area +        link.delete +      end +    end +    children.each do |child| +      child.clean_invalid_access_links +    end +  end + +  def duplicate +    sa = self.deep_clone :except => [:object_version, :parent_id, :registration_number] +    sa.uniq_objectid +    sa.name = I18n.t("activerecord.copy", :name => self.name) +    sa +  end + +  def journey_patterns_control_route_sections +    if self.changed_attributes['latitude'] || self.changed_attributes['longitude'] +      self.stop_points.each do |stop_point| +        stop_point.route.journey_patterns.completed.map{ |jp| jp.control! } +      end +    end +  end + +end diff --git a/app/models/chouette/stop_point.rb b/app/models/chouette/stop_point.rb new file mode 100644 index 000000000..b77189fc1 --- /dev/null +++ b/app/models/chouette/stop_point.rb @@ -0,0 +1,41 @@ +module Chouette +  class StopPoint < TridentActiveRecord +    include ForBoardingEnumerations +    include ForAlightingEnumerations +     +    # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +    self.primary_key = "id" + +    belongs_to :stop_area +    belongs_to :route, inverse_of: :stop_points +    has_many :vehicle_journey_at_stops, :dependent => :destroy +    has_many :vehicle_journeys, -> {uniq}, :through => :vehicle_journey_at_stops + +    acts_as_list :scope => :route, top_of_list: 0 + +    validates_presence_of :stop_area +    validate :stop_area_id_validation + +    scope :default_order, order("position") + +    before_destroy :remove_dependent_journey_pattern_stop_points +    def remove_dependent_journey_pattern_stop_points +      route.journey_patterns.each do |jp| +        if jp.stop_point_ids.include?( id) +          jp.stop_point_ids = jp.stop_point_ids - [id] +        end +      end +    end   + +    def stop_area_id_validation +      if stop_area_id.nil? +        errors.add(:stop_area_id, I18n.t("errors.messages.empty")) +      end +    end + +    def self.area_candidates +      Chouette::StopArea.where( :area_type => ['Quay', 'BoardingPosition']) +    end + +  end +end diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb new file mode 100644 index 000000000..033c39f1c --- /dev/null +++ b/app/models/chouette/time_table.rb @@ -0,0 +1,450 @@ +class Chouette::TimeTable < Chouette::TridentActiveRecord +  # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +  self.primary_key = "id" + +  acts_as_taggable +   +  attr_accessor :monday,:tuesday,:wednesday,:thursday,:friday,:saturday,:sunday   +  attr_accessor :tag_search + +  def self.ransackable_attributes auth_object = nil +    (column_names + ['tag_search']) + _ransackers.keys +  end + +  has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney' + +  has_many :dates, -> {order(:date)}, inverse_of: :time_table, :validate => :true, :class_name => "Chouette::TimeTableDate", :dependent => :destroy +  has_many :periods, -> {order(:period_start)}, inverse_of: :time_table, :validate => :true, :class_name => "Chouette::TimeTablePeriod", :dependent => :destroy + +  after_save :save_shortcuts + +  def self.object_id_key +    "Timetable" +  end + +  accepts_nested_attributes_for :dates, :allow_destroy => :true +  accepts_nested_attributes_for :periods, :allow_destroy => :true + +  validates_presence_of :comment +  validates_associated :dates +  validates_associated :periods + +  def self.start_validity_period +    [Chouette::TimeTable.minimum(:start_date)].compact.min +  end +  def self.end_validity_period +    [Chouette::TimeTable.maximum(:end_date)].compact.max +  end + +  def save_shortcuts +      shortcuts_update +      self.update_column(:start_date, start_date) +      self.update_column(:end_date, end_date) +  end + +  def shortcuts_update(date=nil) +    dates_array = bounding_dates +    #if new_record? +      if dates_array.empty? +        self.start_date=nil +        self.end_date=nil +      else +        self.start_date=dates_array.min +        self.end_date=dates_array.max +      end +    #else +     # if dates_array.empty? +     #   update_attributes :start_date => nil, :end_date => nil +     # else +     #   update_attributes :start_date => dates_array.min, :end_date => dates_array.max +     # end +    #end +  end + +  def validity_out_from_on?(expected_date) +    return false unless self.end_date +    self.end_date <= expected_date +  end +  def validity_out_between?(starting_date, ending_date) +    return false unless self.start_date +    starting_date < self.end_date  && +      self.end_date <= ending_date +  end +  def self.validity_out_from_on?(expected_date,limit=0) +    if limit==0 +      Chouette::TimeTable.where("end_date <= ?", expected_date) +    else +      Chouette::TimeTable.where("end_date <= ?", expected_date).limit( limit) +    end +  end +  def self.validity_out_between?(start_date, end_date,limit=0) +    if limit==0 +      Chouette::TimeTable.where( "? < end_date", start_date).where( "end_date <= ?", end_date) +    else +      Chouette::TimeTable.where( "? < end_date", start_date).where( "end_date <= ?", end_date).limit( limit) +    end +  end + +  # Return days which intersects with the time table dates and periods +  def intersects(days) +    [].tap do |intersect_days| +      days.each do |day| +        intersect_days << day if include_day?(day) +      end +    end +  end + +  def include_day?(day) +    include_in_dates?(day) || include_in_periods?(day) +  end + +  def include_in_dates?(day) +    self.dates.any?{ |d| d.date === day && d.in_out == true } +  end + +  def excluded_date?(day) +    self.dates.any?{ |d| d.date === day && d.in_out == false } +  end + +  def include_in_periods?(day) +    self.periods.any?{ |period| period.period_start <= day && +                                day <= period.period_end && +                                valid_days.include?(day.cwday) && +                                ! excluded_date?(day) } +  end + +  def include_in_overlap_dates?(day) +    return false if self.excluded_date?(day) + +    counter = self.dates.select{ |d| d.date === day}.size + self.periods.select{ |period| period.period_start <= day && day <= period.period_end && valid_days.include?(day.cwday) }.size +    counter <= 1 ? false : true +   end + +  def periods_max_date +    return nil if self.periods.empty? + +    min_start = self.periods.map(&:period_start).compact.min +    max_end = self.periods.map(&:period_end).compact.max +    result = nil + +    if max_end && min_start +      max_end.downto( min_start) do |date| +        if self.valid_days.include?(date.cwday) && !self.excluded_date?(date) +            result = date +            break +        end +      end +    end +    result +  end +  def periods_min_date +    return nil if self.periods.empty? + +    min_start = self.periods.map(&:period_start).compact.min +    max_end = self.periods.map(&:period_end).compact.max +    result = nil + +    if max_end && min_start +      min_start.upto(max_end) do |date| +        if self.valid_days.include?(date.cwday) && !self.excluded_date?(date) +            result = date +            break +        end +      end +    end +    result +  end +  def bounding_dates +    bounding_min = self.dates.select{|d| d.in_out}.map(&:date).compact.min +    bounding_max = self.dates.select{|d| d.in_out}.map(&:date).compact.max + +    unless self.periods.empty? +      bounding_min = periods_min_date if periods_min_date && +          (bounding_min.nil? || (periods_min_date < bounding_min)) + +      bounding_max = periods_max_date if periods_max_date && +          (bounding_max.nil? || (bounding_max < periods_max_date)) +    end + +    [bounding_min, bounding_max].compact +  end + +  def day_by_mask(flag) +    int_day_types & flag == flag +  end + +  def self.day_by_mask(int_day_types,flag) +    int_day_types & flag == flag +  end + + +  def valid_days +    # Build an array with day of calendar week (1-7, Monday is 1). +    [].tap do |valid_days| +      valid_days << 1  if monday +      valid_days << 2  if tuesday +      valid_days << 3  if wednesday +      valid_days << 4  if thursday +      valid_days << 5  if friday +      valid_days << 6  if saturday +      valid_days << 7  if sunday +    end +  end + +  def self.valid_days(int_day_types) +    # Build an array with day of calendar week (1-7, Monday is 1). +    [].tap do |valid_days| +      valid_days << 1  if day_by_mask(int_day_types,4) +      valid_days << 2  if day_by_mask(int_day_types,8) +      valid_days << 3  if day_by_mask(int_day_types,16) +      valid_days << 4  if day_by_mask(int_day_types,32) +      valid_days << 5  if day_by_mask(int_day_types,64) +      valid_days << 6  if day_by_mask(int_day_types,128) +      valid_days << 7  if day_by_mask(int_day_types,256) +    end +  end + +  def monday +    day_by_mask(4) +  end +  def tuesday +    day_by_mask(8) +  end +  def wednesday +    day_by_mask(16) +  end +  def thursday +    day_by_mask(32) +  end +  def friday +    day_by_mask(64) +  end +  def saturday +    day_by_mask(128) +  end +  def sunday +    day_by_mask(256) +  end + +  def set_day(day,flag) +    if day == '1' || day == true +      self.int_day_types |= flag +    else +      self.int_day_types &= ~flag +    end +    shortcuts_update +  end + +  def monday=(day) +    set_day(day,4) +  end +  def tuesday=(day) +    set_day(day,8) +  end +  def wednesday=(day) +    set_day(day,16) +  end +  def thursday=(day) +    set_day(day,32) +  end +  def friday=(day) +    set_day(day,64) +  end +  def saturday=(day) +    set_day(day,128) +  end +  def sunday=(day) +    set_day(day,256) +  end + +  def effective_days_of_period(period,valid_days=self.valid_days) +    days = [] +      period.period_start.upto(period.period_end) do |date| +        if valid_days.include?(date.cwday) && !self.excluded_date?(date) +            days << date +        end +      end +    days +  end + +  def effective_days(valid_days=self.valid_days) +    days=self.effective_days_of_periods(valid_days) +    self.dates.each do |d| +      days |= [d.date] if d.in_out +    end +    days.sort +  end + +  def effective_days_of_periods(valid_days=self.valid_days) +    days = [] +    self.periods.each { |p| days |= self.effective_days_of_period(p,valid_days)} +    days.sort +  end + +  def clone_periods +    periods = [] +    self.periods.each { |p| periods << p.copy} +    periods +  end + +  def included_days +    days = [] +    self.dates.each do |d| +      days |= [d.date] if d.in_out +    end +    days.sort +  end + +  def excluded_days +    days = [] +    self.dates.each do |d| +      days |= [d.date] unless d.in_out +    end +    days.sort +  end + + +  # produce a copy of periods without anyone overlapping or including another +  def optimize_periods +    periods = self.clone_periods +    optimized = [] +    i=0 +    while i < periods.length +      p1 = periods[i] +      optimized << p1 +      j= i+1 +      while j < periods.length +        p2 = periods[j] +        if p1.contains? p2 +          periods.delete p2 +        elsif p1.overlap? p2 +          p1.period_start = [p1.period_start,p2.period_start].min +          p1.period_end = [p1.period_end,p2.period_end].max +          periods.delete p2 +        else +          j += 1 +        end +      end +      i+= 1 +    end +    optimized.sort { |a,b| a.period_start <=> b.period_start} +  end + +  # add a peculiar day or switch it from excluded to included +  def add_included_day(d) +    if self.excluded_date?(d) +       self.dates.each do |date| +         if date.date === d +           date.in_out = true +         end +       end +    elsif !self.include_in_dates?(d) +       self.dates << Chouette::TimeTableDate.new(:date => d, :in_out => true) +    end +  end + +  # merge effective days from another timetable +  def merge!(another_tt) +    transaction do +    # if one tt has no period, just merge lists +    if self.periods.empty? || another_tt.periods.empty? +      if !another_tt.periods.empty? +        # copy periods +        self.periods = another_tt.clone_periods +        # set valid_days +        self.int_day_types = another_tt.int_day_types +      end +      # merge dates +      self.dates ||= [] +      another_tt.included_days.each do |d| +        add_included_day d +      end +    else +      # check if periods can be kept +      common_day_types = self.int_day_types & another_tt.int_day_types & 508 +      # if common day types : merge periods +      if common_day_types != 0 +        periods = self.optimize_periods +        another_periods = another_tt.optimize_periods +        # add not common days of both periods as peculiar days +        self.effective_days_of_periods(self.class.valid_days(self.int_day_types ^ common_day_types)).each do |d| +          self.dates |= [Chouette::TimeTableDate.new(:date => d, :in_out => true)] +        end +        another_tt.effective_days_of_periods(self.class.valid_days(another_tt.int_day_types ^ common_day_types)).each do |d| +          add_included_day d +        end +        # merge periods +        self.periods = periods | another_periods +        self.int_day_types = common_day_types +        self.periods = self.optimize_periods +      else +        # convert all period in days +        self.effective_days_of_periods.each do |d| +          self.dates << Chouette::TimeTableDate.new(:date => d, :in_out => true) unless self.include_in_dates?(d) +        end +        another_tt.effective_days_of_periods.each do |d| +          add_included_day d +        end +      end +    end +    # if remained excluded dates are valid in other tt , remove it from result +    self.dates.each do |date| +      date.in_out = true if date.in_out == false && another_tt.include_day?(date.date) +    end + +    # if peculiar dates are valid in new periods, remove them +    if !self.periods.empty? +      days_in_period = self.effective_days_of_periods +      dates = [] +      self.dates.each do |date| +        dates << date unless date.in_out && days_in_period.include?(date.date) +      end +      self.dates = dates +    end +    self.dates.to_a.sort! { |a,b| a.date <=> b.date} +    self.save! +    end +  end + +  # remove dates form tt which aren't in another_tt +  def intersect!(another_tt) +    transaction do +       +      # transform tt as effective dates and get common ones +      days = another_tt.intersects(self.effective_days) & self.intersects(another_tt.effective_days) +      self.dates.clear +      days.each {|d| self.dates << Chouette::TimeTableDate.new( :date =>d, :in_out => true)} +      self.periods.clear +      self.int_day_types = 0 +      self.dates.to_a.sort! { |a,b| a.date <=> b.date} +      self.save! +    end +  end + + +  def disjoin!(another_tt) +    transaction do +      # remove days from another calendar +      days_to_exclude = self.intersects(another_tt.effective_days) +      days = self.effective_days - days_to_exclude +      self.dates.clear +      self.periods.clear +      self.int_day_types = 0 + +      days.each {|d| self.dates << Chouette::TimeTableDate.new( :date =>d, :in_out => true)} + +      self.dates.to_a.sort! { |a,b| a.date <=> b.date} +      self.periods.to_a.sort! { |a,b| a.period_start <=> b.period_start} +      self.save! +    end +  end + +  def duplicate +    tt = self.deep_clone :include => [:periods, :dates], :except => :object_version +    tt.uniq_objectid +    tt.comment = I18n.t("activerecord.copy", :name => self.comment) +    tt +  end + +end + diff --git a/app/models/chouette/time_table_date.rb b/app/models/chouette/time_table_date.rb new file mode 100644 index 000000000..4624ae88e --- /dev/null +++ b/app/models/chouette/time_table_date.rb @@ -0,0 +1,14 @@ +class Chouette::TimeTableDate < Chouette::ActiveRecord +  self.primary_key = "id" +  belongs_to :time_table, inverse_of: :dates +  acts_as_list :scope => 'time_table_id = #{time_table_id}',:top_of_list => 0 + +  validates_presence_of :date +  validates_uniqueness_of :date, :scope => :time_table_id + +  def self.model_name +    ActiveModel::Name.new Chouette::TimeTableDate, Chouette, "TimeTableDate" +  end + +end + diff --git a/app/models/chouette/time_table_period.rb b/app/models/chouette/time_table_period.rb new file mode 100644 index 000000000..0a75c18c3 --- /dev/null +++ b/app/models/chouette/time_table_period.rb @@ -0,0 +1,40 @@ +class Chouette::TimeTablePeriod < Chouette::ActiveRecord +  self.primary_key = "id" +  belongs_to :time_table, inverse_of: :periods +  acts_as_list :scope => 'time_table_id = #{time_table_id}',:top_of_list => 0 + +  validates_presence_of :period_start +  validates_presence_of :period_end +   +  validate :start_must_be_before_end + +   +  def self.model_name +    ActiveModel::Name.new Chouette::TimeTablePeriod, Chouette, "TimeTablePeriod" +  end +   +  def start_must_be_before_end +    # security against nil values +    if period_end.nil? || period_start.nil? +      return +    end +    if period_end <= period_start +      errors.add(:period_end,I18n.t("activerecord.errors.models.time_table_period.start_must_be_before_end")) +    end +  end +     +  def copy +    Chouette::TimeTablePeriod.new(:period_start => self.period_start,:period_end => self.period_end) +  end +   +  # Test to see if a period overlap this period +  def overlap?(p) +     (p.period_start >= self.period_start && p.period_start <= self.period_end) || (p.period_end >= self.period_start && p.period_end <= self.period_end) +  end + +  # Test to see if a period is included in this period +  def contains?(p) +     (p.period_start >= self.period_start && p.period_end <= self.period_end) +  end +   +end diff --git a/app/models/chouette/timeband.rb b/app/models/chouette/timeband.rb new file mode 100644 index 000000000..9844dd1b1 --- /dev/null +++ b/app/models/chouette/timeband.rb @@ -0,0 +1,30 @@ +module Chouette + +  class TimebandValidator < ActiveModel::Validator +    def validate(record) +      if record.end_time <= record.start_time +        record.errors[:end_time] << I18n.t('activerecord.errors.models.timeband.start_must_be_before_end') +      end +    end +  end + +  class Timeband < Chouette::TridentActiveRecord +    self.primary_key = "id" + +    validates :start_time, :end_time, presence: true +    validates_with TimebandValidator + +    default_scope { order(:start_time) } + +    def self.object_id_key +      "Timeband" +    end + +    def fullname +      fullname = "#{I18n.l(self.start_time, format: :hour)}-#{I18n.l(self.end_time, format: :hour)}" +      "#{self.name} (#{fullname})" if self.name +    end + +  end + +end diff --git a/app/models/chouette/transport_mode.rb b/app/models/chouette/transport_mode.rb new file mode 100644 index 000000000..825ef15b8 --- /dev/null +++ b/app/models/chouette/transport_mode.rb @@ -0,0 +1,71 @@ +class Chouette::TransportMode < ActiveSupport::StringInquirer + +  def initialize(text_code, numerical_code) +    super text_code.to_s +    @numerical_code = numerical_code +  end + +  def self.new(text_code, numerical_code = nil) +    if text_code and numerical_code +      super +    elsif self === text_code  +      text_code +    else +      if Fixnum === text_code +        text_code, numerical_code = definitions.rassoc(text_code) +      else +        text_code, numerical_code = definitions.assoc(text_code.to_s) +      end + +      super text_code, numerical_code +    end +  end + +  def to_i +    @numerical_code +  end + +  def inspect +    "#{to_s}/#{to_i}" +  end + +  def name +    camelize +  end + +  @@definitions = [ +    ["interchange", -1], +    ["unknown", 0], +    ["coach", 1], +    ["air", 2], +    ["waterborne", 3], +    ["bus", 4], +    ["ferry", 5], +    ["walk", 6], +    ["metro", 7], +    ["shuttle", 8], +    ["rapid_transit", 9], +    ["taxi", 10], +    ["local_train", 11], +    ["train", 12], +    ["long_distance_train", 13], +    ["tramway", 14], +    ["trolleybus", 15], +    ["private_vehicle", 16], +    ["bicycle", 17], +    ["other", 18] +  ] +  cattr_reader :definitions + +  @@all = nil +  def self.all +    @@all ||= definitions.collect do |text_code, numerical_code| +      new(text_code, numerical_code) +    end +  end + +  def public_transport? +    not interchange? +  end + +end diff --git a/app/models/chouette/trident_active_record.rb b/app/models/chouette/trident_active_record.rb new file mode 100644 index 000000000..b89b85863 --- /dev/null +++ b/app/models/chouette/trident_active_record.rb @@ -0,0 +1,132 @@ +class Chouette::TridentActiveRecord < Chouette::ActiveRecord +    before_validation :prepare_auto_columns +    after_validation :reset_auto_columns +     +    after_save :build_objectid + +    self.abstract_class = true +    # +    # triggers to generate objectId and objectVersion  +    # TODO setting prefix in referential object +     +    def self.object_id_key +      model_name +    end +     +    def prefix +      "NINOXE" +    end +    def prepare_auto_columns +      # logger.info 'calling before_validation' +      # logger.info 'start before_validation : '+self.objectid.to_s +      if self.objectid.nil? || self.objectid.blank? +        # if empty, generate a pending objectid which will be completed after creation +        if self.id.nil? +           self.objectid = "#{prefix}:#{self.class.object_id_key}:__pending_id__#{rand(1000)}" +        else +           self.objectid = "#{prefix}:#{self.class.object_id_key}:#{self.id}" +           fix_uniq_objectid +        end +      elsif not self.objectid.include? ':' +        # if one token : technical token : completed by prefix and key +        self.objectid = "#{prefix}:#{self.class.object_id_key}:#{self.objectid}" +      end +      # logger.info 'end before_validation : '+self.objectid +      # initialize or update version +      if self.object_version.nil? +        self.object_version = 1 +      else +        self.object_version += 1 +      end +      self.creation_time = Time.now +      self.creator_id = 'chouette' +    end +     +    def reset_auto_columns +      clean_object_id unless self.errors.nil? || self.errors.empty? +    end +     +    def clean_object_id +      if self.objectid.include?("__pending_id__") +        self.objectid=nil +      end +    end   +     +    def fix_uniq_objectid +        base_objectid = self.objectid.rpartition(":").first +        self.objectid = "#{base_objectid}:#{self.id}" +        if !self.valid? +          base_objectid="#{self.objectid}_" +          cnt=1 +          while !self.valid?  +            self.objectid = "#{base_objectid}#{cnt}" +            cnt += 1 +          end +        end +       +    end +     +    def build_objectid +      #logger.info 'start after_create : '+self.objectid +      if self.objectid.include? ':__pending_id__' +        fix_uniq_objectid +        self.update_attributes( :objectid => self.objectid, :object_version => (self.object_version - 1) ) +      end +      #logger.info 'end after_create : '+self.objectid +    end + +  validates_presence_of :objectid +  validates_uniqueness_of :objectid +  validates_numericality_of :object_version +  validate :objectid_format_compliance + +  def objectid_format_compliance +    if !self.objectid.valid? +      #errors.add(:objectid, "is not a valid ObjectId object") +      errors.add(:objectid,I18n.t("activerecord.errors.models.trident.invalid_object_id",:type => self.class.object_id_key)) +#    else +#      unless self.objectid.object_type==self.class.object_id_key +#        errors.add(:objectid,I18n.t("activerecord.errors.models.trident.invalid_object_id_type",:type => self.class.object_id_key)) +#      end +    end +  end +   +  def uniq_objectid +    i = 0 +    baseobjectid = self.objectid +    while self.class.exists?(:objectid => self.objectid)  +      i += 1 +      self.objectid = baseobjectid+"_"+i.to_s +    end +  end + +  def self.model_name +    ActiveModel::Name.new self, Chouette, self.name.demodulize +  end + +  def objectid +    Chouette::ObjectId.new read_attribute(:objectid) +  end + +#  def version +#    self.object_version +#  end + +#  def version=(version) +#    self.object_version = version +#  end + +  before_validation :default_values, :on => :create +  def default_values +    self.object_version ||= 1 +  end + +  def timestamp_attributes_for_update #:nodoc: +    [:creation_time] +  end +   +  def timestamp_attributes_for_create #:nodoc: +    [:creation_time] +  end + +end diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb new file mode 100644 index 000000000..44a9f8975 --- /dev/null +++ b/app/models/chouette/vehicle_journey.rb @@ -0,0 +1,114 @@ +module Chouette +  class VehicleJourney < TridentActiveRecord + +    # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +    self.primary_key = "id" + +    enum journey_category: { timed: 0, frequency: 1 } + +    default_scope { where(journey_category: journey_categories[:timed]) } + +    attr_accessor :transport_mode_name +    attr_reader :time_table_tokens + +    def self.nullable_attributes +      [:transport_mode, :published_journey_name, :vehicle_type_identifier, :published_journey_identifier, :comment, :status_value] +    end + +    belongs_to :company +    belongs_to :route +    belongs_to :journey_pattern + +    has_and_belongs_to_many :footnotes, :class_name => 'Chouette::Footnote' + +    validates_presence_of :route +    validates_presence_of :journey_pattern + +    has_many :vehicle_journey_at_stops, -> { includes(:stop_point).order("stop_points.position") }, :dependent => :destroy +    has_and_belongs_to_many :time_tables, :class_name => 'Chouette::TimeTable', :foreign_key => "vehicle_journey_id", :association_foreign_key => "time_table_id" +    has_many :stop_points, -> { order("stop_points.position") }, :through => :vehicle_journey_at_stops + +    validate :increasing_times +    validates_presence_of :number + +    before_validation :set_default_values +    def set_default_values +      if number.nil? +        self.number = 0 +      end +    end + +    scope :without_any_time_table, -> { joins('LEFT JOIN "time_tables_vehicle_journeys" ON "time_tables_vehicle_journeys"."vehicle_journey_id" = "vehicle_journeys"."id" LEFT JOIN "time_tables" ON "time_tables"."id" = "time_tables_vehicle_journeys"."time_table_id"').where(:time_tables => { :id => nil}) } +    scope :without_any_passing_time, -> { joins('LEFT JOIN "vehicle_journey_at_stops" ON "vehicle_journey_at_stops"."vehicle_journey_id" = "vehicle_journeys"."id"').where(vehicle_journey_at_stops: { id: nil }) } + +    accepts_nested_attributes_for :vehicle_journey_at_stops, :allow_destroy => true + +    def transport_mode_name +      # return nil if transport_mode is nil +      transport_mode && Chouette::TransportMode.new( transport_mode.underscore) +    end + +    def transport_mode_name=(transport_mode_name) +      self.transport_mode = (transport_mode_name ? transport_mode_name.camelcase : nil) +    end + +    @@transport_mode_names = nil +    def self.transport_mode_names +      @@transport_mode_names ||= Chouette::TransportMode.all.select do |transport_mode_name| +        transport_mode_name.to_i > 0 +      end +    end + +    def increasing_times +      previous = nil +      vehicle_journey_at_stops.select{|vjas| vjas.departure_time && vjas.arrival_time}.each do |vjas| +        errors.add( :vehicle_journey_at_stops, 'time gap overflow') unless vjas.increasing_times_validate( previous) +        previous = vjas +      end +    end + +    def missing_stops_in_relation_to_a_journey_pattern(selected_journey_pattern) +      selected_journey_pattern.stop_points - self.stop_points +    end +    def extra_stops_in_relation_to_a_journey_pattern(selected_journey_pattern) +      self.stop_points - selected_journey_pattern.stop_points +    end +    def extra_vjas_in_relation_to_a_journey_pattern(selected_journey_pattern) +      extra_stops = self.extra_stops_in_relation_to_a_journey_pattern(selected_journey_pattern) +      self.vehicle_journey_at_stops.select { |vjas| extra_stops.include?( vjas.stop_point)} +    end +    def time_table_tokens=(ids) +      self.time_table_ids = ids.split(",") +    end +    def bounding_dates +      dates = [] + +      time_tables.each do |tm| +        dates << tm.start_date if tm.start_date +        dates << tm.end_date if tm.end_date +      end + +      dates.empty? ? [] : [dates.min, dates.max] +    end + +    def update_journey_pattern( selected_journey_pattern) +      return unless selected_journey_pattern.route_id==self.route_id + +      missing_stops_in_relation_to_a_journey_pattern(selected_journey_pattern).each do |sp| +        self.vehicle_journey_at_stops.build( :stop_point => sp) +      end +      extra_vjas_in_relation_to_a_journey_pattern(selected_journey_pattern).each do |vjas| +        vjas._destroy = true +      end +    end + +    def self.matrix(vehicle_journeys) +      {}.tap do |hash| +        vehicle_journeys.map{ |vj| +          vj.vehicle_journey_at_stops.map{ |vjas |hash[ "#{vj.id}-#{vjas.stop_point_id}"] = vjas } +        } +      end +    end + +  end +end diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb new file mode 100644 index 000000000..7d6414f55 --- /dev/null +++ b/app/models/chouette/vehicle_journey_at_stop.rb @@ -0,0 +1,48 @@ +module Chouette +  class VehicleJourneyAtStop < ActiveRecord +    include ForBoardingEnumerations +    include ForAlightingEnumerations + +    # FIXME http://jira.codehaus.org/browse/JRUBY-6358 +    self.primary_key = "id" + +    belongs_to :stop_point +    belongs_to :vehicle_journey + +    attr_accessor :_destroy + +    validate :arrival_must_be_before_departure +    def arrival_must_be_before_departure +      # security against nil values +      return unless arrival_time && departure_time + +      if exceeds_gap?( arrival_time, departure_time) +        errors.add(:arrival_time,I18n.t("activerecord.errors.models.vehicle_journey_at_stop.arrival_must_be_before_departure")) +      end +    end + +    after_initialize :set_virtual_attributes +    def set_virtual_attributes +      @_destroy = false +    end + +    def increasing_times_validate( previous) +      result = true +      return result unless previous + +      if exceeds_gap?( previous.departure_time, departure_time) +        result = false +        errors.add( :departure_time, 'departure time gap overflow') +      end +      if exceeds_gap?( previous.arrival_time, arrival_time) +        result = false +        errors.add( :arrival_time, 'arrival time gap overflow') +      end +      result +    end +    def exceeds_gap?(first, second) +      (4 * 3600) < ((second - first) % (3600 * 24)) +    end + +  end +end diff --git a/app/models/chouette/vehicle_journey_frequency.rb b/app/models/chouette/vehicle_journey_frequency.rb new file mode 100644 index 000000000..41ba49082 --- /dev/null +++ b/app/models/chouette/vehicle_journey_frequency.rb @@ -0,0 +1,69 @@ +module Chouette +  class VehicleJourneyFrequency < VehicleJourney + +    after_initialize :fill_journey_category + +    default_scope { where(journey_category: journey_categories[:frequency]) } + +    has_many :journey_frequencies, dependent: :destroy, foreign_key: 'vehicle_journey_id' +    accepts_nested_attributes_for :journey_frequencies, allow_destroy: true + +    validate :require_at_least_one_frequency + +    def self.matrix(vehicle_journeys) +      hash = {} +      vehicle_journeys.each do |vj| +        vj.journey_frequencies.each do |jf| +          next if jf.scheduled_headway_interval.hour == 0 && jf.scheduled_headway_interval.min == 0 +          interval = jf.scheduled_headway_interval.hour.hours + jf.scheduled_headway_interval.min.minutes +          first_departure_time = jf.first_departure_time +          while first_departure_time <= jf.last_departure_time +            hash[first_departure_time] = vj +            first_departure_time += interval +          end +        end +      end +      hash.sort.to_h +    end + +    def self.matrix_interval(matrix) +      hash = prepare_matrix(matrix) +      matrix.each do |departure_time, vj| +        @base_departure_time = departure_time +        vj.vehicle_journey_at_stops.each_cons(2) { |vjas, vjas_next| +          vjas_dup = vjas.dup +          vjas_dup.departure_time = @base_departure_time +          hash[vjas.stop_point.stop_area.name][departure_time.to_i] = vjas_dup +          @base_departure_time = vjas_dup.departure_time + (vjas_next.departure_time - vjas.departure_time) +          @last_vjas_next = vjas_next.dup +        } +        # Add last stop_point +        @last_vjas_next.departure_time = @base_departure_time +        hash[@last_vjas_next.stop_point.stop_area.name][departure_time.to_i] = @last_vjas_next +      end +      hash +    end + +    private + +    def self.prepare_matrix(matrix) +      matrix.map{ |departure_time, vj| +        Hash[ +          vj.vehicle_journey_at_stops.map{ |sp| +            [ +              sp.stop_point.stop_area.name, Hash[matrix.map{ |departure_time2, vj2| [departure_time2.to_i, nil] }] +            ] +          } +        ] +      }.inject(&:merge) +    end + +    def fill_journey_category +      self.journey_category = :frequency +    end + +    def require_at_least_one_frequency +      errors.add(:base, I18n.t('vehicle_journey_frequency.require_at_least_one_frequency')) unless journey_frequencies.size > 0 +    end +  end +end diff --git a/app/models/chouette/wayback.rb b/app/models/chouette/wayback.rb new file mode 100644 index 000000000..b2950449d --- /dev/null +++ b/app/models/chouette/wayback.rb @@ -0,0 +1,50 @@ +class Chouette::Wayback < ActiveSupport::StringInquirer + +  def initialize(text_code, numerical_code) +    super text_code.to_s +    @numerical_code = numerical_code +  end + +  def self.new(text_code, numerical_code = nil) +    if text_code and numerical_code +      super +    elsif self === text_code  +      text_code +    else +      if Fixnum === text_code +        text_code, numerical_code = definitions.rassoc(text_code) +      else +        text_code, numerical_code = definitions.assoc(text_code.to_s) +      end + +      super text_code, numerical_code +    end +  end + +  def to_i +    @numerical_code +  end + +  def inspect +    "#{to_s}/#{to_i}" +  end + +  def name +    to_s +  end + +  @@definitions = [ +    ["straight_forward", 0], +    ["backward", 1] +  ] +  cattr_reader :definitions + +  @@all = nil +  def self.all +    @@all ||= definitions.collect do |text_code, numerical_code| +      new(text_code, numerical_code) +    end +  end + +end + diff --git a/app/presenters/chouette/geometry/access_link_presenter.rb b/app/presenters/chouette/geometry/access_link_presenter.rb new file mode 100644 index 000000000..b0f1d5f94 --- /dev/null +++ b/app/presenters/chouette/geometry/access_link_presenter.rb @@ -0,0 +1,11 @@ +class Chouette::Geometry::AccessLinkPresenter +  include Chouette::Geometry::GeneralPresenter + +  def initialize(access_link) +    @access_link = access_link +  end + +  def geometry +    to_line_string_feature( [ @access_link.stop_area , @access_link.access_point ] ) +  end +end diff --git a/app/presenters/chouette/geometry/access_point_presenter.rb b/app/presenters/chouette/geometry/access_point_presenter.rb new file mode 100644 index 000000000..d3ecb6128 --- /dev/null +++ b/app/presenters/chouette/geometry/access_point_presenter.rb @@ -0,0 +1,11 @@ +class Chouette::Geometry::AccessPointPresenter +  include Chouette::Geometry::GeneralPresenter + +  def initialize(access_point) +    @access_point = access_point +  end + +  def geometry +    to_point_feature( @access_point) +  end +end diff --git a/app/presenters/chouette/geometry/connection_link_presenter.rb b/app/presenters/chouette/geometry/connection_link_presenter.rb new file mode 100644 index 000000000..10f7fcd17 --- /dev/null +++ b/app/presenters/chouette/geometry/connection_link_presenter.rb @@ -0,0 +1,11 @@ +class Chouette::Geometry::ConnectionLinkPresenter +  include Chouette::Geometry::GeneralPresenter + +  def initialize(connection_link) +    @connection_link = connection_link +  end + +  def geometry +    to_line_string_feature( @connection_link.stop_areas) +  end +end diff --git a/app/presenters/chouette/geometry/general_presenter.rb b/app/presenters/chouette/geometry/general_presenter.rb new file mode 100644 index 000000000..3f0dd0031 --- /dev/null +++ b/app/presenters/chouette/geometry/general_presenter.rb @@ -0,0 +1,20 @@ +module Chouette::Geometry::GeneralPresenter + +  def to_line_string_feature( stop_areas) +    points = stop_areas.collect(&:geometry).compact +    GeoRuby::SimpleFeatures::LineString.from_points(points) +  end + +  def to_multi_point_feature( stop_areas) +    points = stop_areas.collect(&:geometry).compact +    GeoRuby::SimpleFeatures::MultiPoint.from_points( points ) +  end + +  def to_point_feature( stop_area) +    return nil unless stop_area.longitude && stop_area.latitude +    GeoRuby::SimpleFeatures::Point.from_lon_lat( stop_area.longitude, stop_area.latitude, 4326) +  end + +end + + diff --git a/app/presenters/chouette/geometry/line_presenter.rb b/app/presenters/chouette/geometry/line_presenter.rb new file mode 100644 index 000000000..89ab39185 --- /dev/null +++ b/app/presenters/chouette/geometry/line_presenter.rb @@ -0,0 +1,42 @@ +class Chouette::Geometry::LinePresenter +  include Chouette::Geometry::GeneralPresenter + +  def initialize(line) +    @line = line +  end + +  # return line geometry based on CommercialStopPoint +  # +  def geometry +    features = commercial_links.map { |link| to_line_string_feature(link) } +    GeoRuby::SimpleFeatures::MultiLineString.from_line_strings( features, 4326) +  end +  # +  # return line's stop_areas cloud geometry +  # +  def stop_areas_geometry +    to_multi_point_feature( @line.commercial_stop_areas) +  end + +  def commercial_links +    link_keys = [] +    [].tap do |stop_area_links| +      @line.routes.each do |route| +        previous_commercial = nil +        routes_localized_commercials(route).each do |commercial| +          if previous_commercial && !link_keys.include?( "#{previous_commercial.id}-#{commercial.id}") +            stop_area_links << [ previous_commercial, commercial] +            link_keys << "#{previous_commercial.id}-#{commercial.id}" +            link_keys << "#{commercial.id}-#{previous_commercial.id}" +          end +          previous_commercial = commercial +        end +      end +    end +  end + +  def routes_localized_commercials(route) +    route.stop_areas.map { |sa| sa.parent}.compact.select { |sa| sa.latitude && sa.longitude} +  end + +end diff --git a/app/presenters/chouette/geometry/route_presenter.rb b/app/presenters/chouette/geometry/route_presenter.rb new file mode 100644 index 000000000..292548c91 --- /dev/null +++ b/app/presenters/chouette/geometry/route_presenter.rb @@ -0,0 +1,22 @@ +class Chouette::Geometry::RoutePresenter +  include Chouette::Geometry::GeneralPresenter + +  def initialize(route) +    @route = route +  end + +  # return route's stop_areas cloud geometry +  # +  def stop_areas_geometry +    to_multi_point_feature( @route.stop_areas.with_geometry ) +  end + +  # return route geometry based on BoardingPosition or Quay +  # +  def geometry +    to_line_string_feature( @route.stop_areas.with_geometry ) +  end + + +end + diff --git a/app/presenters/chouette/geometry/stop_area_presenter.rb b/app/presenters/chouette/geometry/stop_area_presenter.rb new file mode 100644 index 000000000..195405eab --- /dev/null +++ b/app/presenters/chouette/geometry/stop_area_presenter.rb @@ -0,0 +1,13 @@ +class Chouette::Geometry::StopAreaPresenter +  include Chouette::Geometry::GeneralPresenter + +  def initialize(stop_area) +    @stop_area = stop_area +  end + +  # return line geometry based on CommercialStopPoint +  # +  def geometry +    to_point_feature( @stop_area) +  end +end diff --git a/config/locales/ninoxe.en.yml b/config/locales/ninoxe.en.yml new file mode 100644 index 000000000..134ef4f66 --- /dev/null +++ b/config/locales/ninoxe.en.yml @@ -0,0 +1,23 @@ +en: +  activerecord: +    copy: "Copy of %{name}" +    errors: +      models: +        trident: +          invalid_object_id: "invalid syntax, expected as [A-Za-z0-9_]:%{type}:[A-Za-z0-9_-]" +          invalid_object_id_type: invalid type, must be %{type} +        time_table_period: +          start_must_be_before_end: end date must be after start date +        time_table_date:  +          attributes: +            date: +              taken: dupplicate date for this timetable +        vehicle_journey_at_stop: +          arrival_must_be_before_departure: arrival time must be before departure time +        timeband: +          start_must_be_before_end: end date must be after start date +        journey_frequency: +          start_must_be_after_timeband: the date of departure must be greater or equal to the time bands +          end_must_be_before_timeband: the end date must be less than or equal to the time bands +          end_must_be_different_from_first: the end date must be different from first date +          scheduled_headway_interval_greater_than_zero: interval must be greater than 0 diff --git a/config/locales/ninoxe.fr.yml b/config/locales/ninoxe.fr.yml new file mode 100644 index 000000000..3a823465d --- /dev/null +++ b/config/locales/ninoxe.fr.yml @@ -0,0 +1,23 @@ +fr: +  activerecord: +    copy: "Copie de %{name}" +    errors: +      models: +        trident: +          invalid_object_id: "syntaxe invalide, [A-Za-z0-9_]:%{type}:[A-Za-z0-9_-] attendu" +          invalid_object_id_type: type invalide, %{type} attendu +        time_table_period: +          start_must_be_before_end: la date de fin doit être postérieure à la date de début +        time_table_date:  +          attributes: +            date: +              taken: date déjà saisie pour ce calendrier +        vehicle_journey_at_stop: +          arrival_must_be_before_departure: "l'heure d'arrivée doit être antérieure à l'heure de départ" +        timeband: +          start_must_be_before_end: la date de fin doit être postérieure à la date de début +        journey_frequency: +          start_must_be_after_timeband: la date de départ doit être supérieure ou égal à la plage horaire +          end_must_be_before_timeband: la date de fin doit être inférieur ou égal à la plage horaire +          end_must_be_different_from_first: la date de fin doit être différent de la date de départ +          scheduled_headway_interval_greater_than_zero: l'intervalle doit être supérieur à 0 diff --git a/spec/factories.rb b/spec/factories/chouette_2_factories.rb index 44179b563..44179b563 100644 --- a/spec/factories.rb +++ b/spec/factories/chouette_2_factories.rb diff --git a/spec/factories/chouette_access_links.rb b/spec/factories/chouette_access_links.rb new file mode 100644 index 000000000..94717e95e --- /dev/null +++ b/spec/factories/chouette_access_links.rb @@ -0,0 +1,13 @@ +FactoryGirl.define do +   +  factory :access_link, :class => Chouette::AccessLink do +    sequence(:name) { |n| "Access link #{n}" } +    sequence(:objectid) { |n| "test:AccessLink:#{n}" } +    link_type "Mixed"  +    link_orientation "AccessPointToStopArea" + +    association :stop_area, :factory => :stop_area +    association :access_point, :factory => :access_point +  end + +end diff --git a/spec/factories/chouette_access_points.rb b/spec/factories/chouette_access_points.rb new file mode 100644 index 000000000..06d1da779 --- /dev/null +++ b/spec/factories/chouette_access_points.rb @@ -0,0 +1,12 @@ +FactoryGirl.define do + +  factory :access_point, :class => Chouette::AccessPoint do +    latitude {10.0 * rand} +    longitude {10.0 * rand} +    sequence(:name) { |n| "AccessPoint #{n}" } +    access_type "InOut" +    sequence(:objectid) { |n| "test:AccessPoint:#{n}" } +    association :stop_area, :factory => :stop_area +  end + +end diff --git a/spec/factories/chouette_companies.rb b/spec/factories/chouette_companies.rb new file mode 100644 index 000000000..c0e46fe42 --- /dev/null +++ b/spec/factories/chouette_companies.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + +  factory :company, :class => Chouette::Company do +    sequence(:name) { |n| "Company #{n}" } +    sequence(:objectid) { |n| "test:Company:#{n}" } +    sequence(:registration_number) { |n| "test-#{n}" } +  end + +end diff --git a/spec/factories/chouette_connection_links.rb b/spec/factories/chouette_connection_links.rb new file mode 100644 index 000000000..f70548721 --- /dev/null +++ b/spec/factories/chouette_connection_links.rb @@ -0,0 +1,13 @@ +FactoryGirl.define do + +  factory :connection_link, :class => Chouette::ConnectionLink do +    sequence(:name) { |n| "Connection link #{n}" } +    sequence(:link_type) { |n| "Mixed" } +    sequence(:objectid) { |n| "test:ConnectionLink:#{n}" } + +    association :departure, :factory => :stop_area +    association :arrival, :factory => :stop_area +  end +   +end + diff --git a/spec/factories/chouette_footnotes.rb b/spec/factories/chouette_footnotes.rb new file mode 100644 index 000000000..d30e305ee --- /dev/null +++ b/spec/factories/chouette_footnotes.rb @@ -0,0 +1,10 @@ +FactoryGirl.define do + +  factory :footnote, :class => Chouette::Footnote do +    sequence(:code) { |n| "#{n}" } +    sequence(:label) { |n| "footnote #{n}" } +    association :line, :factory => :line +  end + +end + diff --git a/spec/factories/chouette_group_of_lines.rb b/spec/factories/chouette_group_of_lines.rb new file mode 100644 index 000000000..3309674e3 --- /dev/null +++ b/spec/factories/chouette_group_of_lines.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + +  factory  :group_of_line, :class => Chouette::GroupOfLine do +    sequence(:name) { |n| "Group Of Line #{n}" } +    sequence(:objectid) { |n| "test:GroupOfLine:#{n}" } +    sequence(:registration_number) { |n| "#{n}" } +  end + +end diff --git a/spec/factories/chouette_journey_frequency.rb b/spec/factories/chouette_journey_frequency.rb new file mode 100644 index 000000000..aa28bdefe --- /dev/null +++ b/spec/factories/chouette_journey_frequency.rb @@ -0,0 +1,38 @@ +FactoryGirl.define do + +  factory :journey_frequency, class: Chouette::JourneyFrequency do +    timeband +    scheduled_headway_interval { Time.current } +    first_departure_time { timeband.start_time } +    last_departure_time { timeband.end_time } +  end + +  factory :journey_frequency_first_departure_time_invalid, class: Chouette::JourneyFrequency do +    timeband +    scheduled_headway_interval { Time.current } +    first_departure_time { timeband.start_time - 1.minute } +    last_departure_time { timeband.end_time } +  end + +  factory :journey_frequency_last_departure_time_invalid, class: Chouette::JourneyFrequency do +    timeband +    scheduled_headway_interval { Time.current } +    first_departure_time { timeband.start_time } +    last_departure_time { timeband.end_time + 1.minute } +  end + +  factory :journey_frequency_departure_time_invalid, class: Chouette::JourneyFrequency do +    timeband +    scheduled_headway_interval { Time.current } +    first_departure_time { '00:00' } +    last_departure_time { '00:00' } +  end + +  factory :journey_frequency_scheduled_headway_interval_invalid, class: Chouette::JourneyFrequency do +    timeband +    scheduled_headway_interval { '00:00' } +    first_departure_time { timeband.start_time } +    last_departure_time { timeband.end_time } +  end + +end diff --git a/spec/factories/chouette_journey_pattern.rb b/spec/factories/chouette_journey_pattern.rb new file mode 100644 index 000000000..bf55b286f --- /dev/null +++ b/spec/factories/chouette_journey_pattern.rb @@ -0,0 +1,40 @@ +FactoryGirl.define do +   +  factory :journey_pattern_common, :class => Chouette::JourneyPattern do +    sequence(:name) { |n| "jp name #{n}" } +    sequence(:published_name) { |n| "jp publishedname #{n}" } +    sequence(:comment) { |n| "jp comment #{n}" } +    sequence(:registration_number) { |n| "jp registration_number #{n}" } +    sequence(:objectid) { |n| "test:JourneyPattern:#{n}" } +     +    association :route, :factory => :route +     +    factory :journey_pattern do +      after(:create) do |j| +        j.stop_point_ids = j.route.stop_points.map(&:id) +        j.departure_stop_point_id = j.route.stop_points.first.id +        j.arrival_stop_point_id = j.route.stop_points.last.id +      end +    end +     +    factory :journey_pattern_odd do +      after(:create) do |j| +        j.stop_point_ids = j.route.stop_points.select { |sp| sp.position%2==0}.map(&:id) +        j.departure_stop_point_id = j.stop_point_ids.first +        j.arrival_stop_point_id = j.stop_point_ids.last +      end +    end +     +    factory :journey_pattern_even do +      after(:create) do |j| +        j.stop_point_ids = j.route.stop_points.select { |sp| sp.position%2==1}.map(&:id) +        j.departure_stop_point_id = j.stop_point_ids.first +        j.arrival_stop_point_id = j.stop_point_ids.last +      end +    end + +  end + +end + + diff --git a/spec/factories/chouette_lines.rb b/spec/factories/chouette_lines.rb new file mode 100644 index 000000000..9a5842458 --- /dev/null +++ b/spec/factories/chouette_lines.rb @@ -0,0 +1,44 @@ +FactoryGirl.define do + +  factory :line, :class => Chouette::Line do +    sequence(:name) { |n| "Line #{n}" } +    sequence(:objectid) { |n| "test:Line:#{n}" } +    sequence(:transport_mode_name) { |n| "Bus" } + +    association :network, :factory => :network +    association :company, :factory => :company + +    sequence(:registration_number) { |n| "test-#{n}" } +     +    factory :line_with_stop_areas do +       +      transient do +        routes_count 2 +        stop_areas_count 5 +      end +       +      after(:create) do |line, evaluator| +        create_list(:route, evaluator.routes_count, :line => line) do |route| +          create_list(:stop_area, evaluator.stop_areas_count, area_type: "Quay") do |stop_area| +            create(:stop_point, :stop_area => stop_area, :route => route) +          end            +        end +      end +       +      factory :line_with_stop_areas_having_parent do +         +        after(:create) do |line| +          line.routes.each do |route| +            route.stop_points.each do |stop_point| +              comm = create(:stop_area, :area_type => "CommercialStopPoint") +              stop_point.stop_area.update_attributes(:parent_id => comm.id) +            end +          end +        end +      end + +    end + +  end + +end diff --git a/spec/factories/chouette_networks.rb b/spec/factories/chouette_networks.rb new file mode 100644 index 000000000..8f9bfea51 --- /dev/null +++ b/spec/factories/chouette_networks.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + +  factory :network, :class => Chouette::Network do +    sequence(:name) { |n| "Network #{n}" } +    sequence(:objectid) { |n| "test:GroupOfLine:#{n}" } +    sequence(:registration_number) { |n| "test-#{n}" } +  end + +end diff --git a/spec/factories/chouette_route_sections.rb b/spec/factories/chouette_route_sections.rb new file mode 100644 index 000000000..d5e2cf69d --- /dev/null +++ b/spec/factories/chouette_route_sections.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do +  factory :route_section, :class => Chouette::RouteSection do +    association :departure, :factory => :stop_area +    association :arrival, :factory => :stop_area +  end +end diff --git a/spec/factories/chouette_routes.rb b/spec/factories/chouette_routes.rb new file mode 100644 index 000000000..047c35912 --- /dev/null +++ b/spec/factories/chouette_routes.rb @@ -0,0 +1,26 @@ +FactoryGirl.define do + +  factory :route_common, :class => Chouette::Route do +    sequence(:name) { |n| "Route #{n}" } +    sequence(:published_name) { |n| "Long route #{n}" } +    sequence(:number) { |n| "#{n}" } +    sequence(:wayback_code) { |n| Chouette::Wayback.new( n % 2) } +    sequence(:direction_code) { |n| Chouette::Direction.new( n % 12) } +    sequence(:objectid) { |n| "test:Route:#{n}" } +     +    association :line, :factory => :line +     +    factory :route do + +      transient do +        stop_points_count 5 +      end +       +      after(:create) do |route, evaluator| +        create_list(:stop_point, evaluator.stop_points_count, route: route) +      end +       +    end +  end +   +end diff --git a/spec/factories/chouette_stop_areas.rb b/spec/factories/chouette_stop_areas.rb new file mode 100644 index 000000000..fab845de5 --- /dev/null +++ b/spec/factories/chouette_stop_areas.rb @@ -0,0 +1,12 @@ +FactoryGirl.define do + +  factory :stop_area, :class => Chouette::StopArea do +    sequence(:objectid) { |n| "test:StopArea:#{n}" } +    sequence(:name) { |n| "stop_area_#{n}" } +    sequence(:registration_number) { |n| "test-#{n}" } +    area_type "CommercialStopPoint" +    latitude {10.0 * rand} +    longitude {10.0 * rand} +  end + +end diff --git a/spec/factories/chouette_stop_points.rb b/spec/factories/chouette_stop_points.rb new file mode 100644 index 000000000..690d1c688 --- /dev/null +++ b/spec/factories/chouette_stop_points.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + +  factory :stop_point, :class => Chouette::StopPoint do +    sequence(:objectid) { |n| "test:StopPoint:#{n}" } +    association :stop_area, :factory => :stop_area +  end + +end diff --git a/spec/factories/chouette_time_table.rb b/spec/factories/chouette_time_table.rb new file mode 100644 index 000000000..722c977b2 --- /dev/null +++ b/spec/factories/chouette_time_table.rb @@ -0,0 +1,37 @@ +FactoryGirl.define do + +  factory :time_table_date, :class => Chouette::TimeTableDate do +  end +   +  factory :time_table_period, :class => Chouette::TimeTablePeriod do +  end +   +  factory :time_table, :class => Chouette::TimeTable do +    sequence(:comment) { |n| "Timetable #{n}" } +    sequence(:objectid) { |n| "test:Timetable:#{n}" } +    sequence(:int_day_types) { (1..7).to_a.map{ |n| 2**(n+1)}.sum } + +    transient do +      dates_count 4 +      periods_count 4 +    end +     +    after(:create) do |time_table, evaluator| +       +      0.upto(4) do |i| +        time_table.dates  << create(:time_table_date, :time_table => time_table, :date => i.days.since.to_date, :in_out => true) +      end +       +      start_date = Date.today +      end_date = start_date + 10 +       +      0.upto(4) do |i| +        time_table.periods << create(:time_table_period, :time_table => time_table, :period_start => start_date, :period_end => end_date) +        start_date = start_date + 20 +        end_date = start_date + 10 +      end +      time_table.save_shortcuts +    end +  end +   +end diff --git a/spec/factories/chouette_timeband.rb b/spec/factories/chouette_timeband.rb new file mode 100644 index 000000000..6e2825c22 --- /dev/null +++ b/spec/factories/chouette_timeband.rb @@ -0,0 +1,17 @@ +FactoryGirl.define do + +  factory :timeband, class: Chouette::Timeband do +    sequence(:name) { |n| "Name: #{n}" } +    start_time { Time.now } +    end_time { Time.now + 1.hour } +    sequence(:objectid) { |n| "test:Timeband:#{n}" } +  end + +  factory :timeband_invalid, class: Chouette::Timeband do +    sequence(:name) { |n| "Name: #{n}" } +    start_time { Time.now + 1.hour } +    end_time { Time.now } +    sequence(:objectid) { |n| "test:Timeband:#{n}" } +  end + +end diff --git a/spec/factories/chouette_vehicle_journey.rb b/spec/factories/chouette_vehicle_journey.rb new file mode 100644 index 000000000..2ad20e3a8 --- /dev/null +++ b/spec/factories/chouette_vehicle_journey.rb @@ -0,0 +1,66 @@ +FactoryGirl.define do +   +  factory :vehicle_journey_common, :class => Chouette::VehicleJourney do +    sequence(:objectid) { |n| "test:VehicleJourney:#{n}" } +     +    factory :vehicle_journey do +      association :journey_pattern, :factory => :journey_pattern +     +      after(:build) do |vehicle_journey| +        vehicle_journey.route = vehicle_journey.journey_pattern.route +      end +       +      after(:create) do |vehicle_journey| +        vehicle_journey.journey_pattern.stop_points.each_with_index do |stop_point, index| +          vehicle_journey.vehicle_journey_at_stops << create(:vehicle_journey_at_stop,  +                 :vehicle_journey => vehicle_journey,  +                 :stop_point => stop_point,  +                 :arrival_time => (-1 * index).minutes.ago,  +                 :departure_time => (-1 * index).minutes.ago) +        end +      end +       +      factory :vehicle_journey_odd do +        association :journey_pattern, :factory => :journey_pattern_odd +      end +       +      factory :vehicle_journey_even do +        association :journey_pattern, :factory => :journey_pattern_even +      end +    end +  end +end + +#      after(:build) do |vehicle_journey| +#        vehicle_journey.route_id = vehicle_journey.journey_pattern.route_id +#      end +#       +#      after(:create) do |vehicle_journey| +#        vehicle_journey.journey_pattern.stop_points.each_with_index do |stop_point, index| +#          vehicle_journey.vehicle_journey_at_stops.create(:vehicle_journey_at_stop,  +#                                                          :vehicle_journey => vehicle_journey,  +#                                                          :stop_point => stop_point,  +#                                                          :arrival_time => (-1 * index).minutes.ago,  +#                                                          :departure_time => (-1 * index).minutes.ago) +#        end +#      end +#    end +#     +#      after(:build) do |vehicle_journey| +#        vehicle_journey.route_id = vehicle_journey.journey_pattern.route_id +#      end +#       +#      after(:create) do |vehicle_journey| +#        vehicle_journey.journey_pattern.stop_points.each_with_index do |stop_point, index| +#          vehicle_journey.vehicle_journey_at_stops.create(:vehicle_journey_at_stop,  +#                                                          :vehicle_journey => vehicle_journey,  +#                                                          :stop_point => stop_point,  +#                                                          :arrival_time => (-1 * index).minutes.ago,  +#                                                          :departure_time => (-1 * index).minutes.ago) +#        end +#      end +#    end +#     +#  end +#end +# diff --git a/spec/factories/chouette_vehicle_journey_at_stop.rb b/spec/factories/chouette_vehicle_journey_at_stop.rb new file mode 100644 index 000000000..c452a1317 --- /dev/null +++ b/spec/factories/chouette_vehicle_journey_at_stop.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do +   +  factory :vehicle_journey_at_stop, :class => Chouette::VehicleJourneyAtStop do +    association :vehicle_journey, :factory => :vehicle_journey +  end + +end + diff --git a/spec/models/chouette/access_link_spec.rb b/spec/models/chouette/access_link_spec.rb new file mode 100644 index 000000000..0e1e91593 --- /dev/null +++ b/spec/models/chouette/access_link_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe Chouette::AccessLink, :type => :model do +  subject { create(:access_link) } + +  it { is_expected.to validate_uniqueness_of :objectid } + +  describe '#objectid' do +    subject { super().objectid } +    it { is_expected.to be_kind_of(Chouette::ObjectId) } +  end + +  it { is_expected.to validate_presence_of :name } +  #it { is_expected.to validate_presence_of :link_type } +  it { is_expected.to validate_presence_of :link_orientation } + +  describe "#access_link_type" do + +    def self.legacy_link_types +      %w{Underground Mixed Overground} +    end +     +    legacy_link_types.each do |link_type| +      context "when link_type is #{link_type}" do +        access_link_type = Chouette::ConnectionLinkType.new(link_type.underscore) +        it "should be #{access_link_type}" do +          subject.link_type = link_type +          expect(subject.access_link_type).to eq(access_link_type) +        end +      end +    end +  end + +  describe "#access_link_type=" do +     +    it "should change link_type with ConnectionLinkType#name" do +      subject.access_link_type = "underground" +      expect(subject.link_type).to eq("Underground") +    end + +  end + +  describe "#link_orientation_type" do + +    def self.legacy_link_orientations +      %w{AccessPointToStopArea StopAreaToAccessPoint} +    end +     +    legacy_link_orientations.each do |link_orientation| +      context "when link_orientation is #{link_orientation}" do +        link_orientation_type = Chouette::LinkOrientationType.new(link_orientation.underscore) +        it "should be #{link_orientation_type}" do +          subject.link_orientation = link_orientation +          expect(subject.link_orientation_type).to eq(link_orientation_type) +        end +      end +    end + +  end + +  describe "#link_orientation_type=" do +     +    it "should change link_orientation with LinkOrientationType#name" do +      subject.link_orientation_type = "access_point_to_stop_area" +      expect(subject.link_orientation).to eq("AccessPointToStopArea") +    end + +  end + +  describe "#link_key" do +    it "should calculate link_key for access to area" do +      subject.link_orientation_type = "access_point_to_stop_area" +      expect(subject.link_key).to eq("A_#{subject.access_point.id}-S_#{subject.stop_area.id}") +    end +    it "should calculate link_key for area to access" do +      subject.link_orientation_type = "stop_area_to_access_point" +      expect(subject.link_key).to eq("S_#{subject.stop_area.id}-A_#{subject.access_point.id}") +    end +     +  end + +end diff --git a/spec/models/chouette/access_point_spec.rb b/spec/models/chouette/access_point_spec.rb new file mode 100644 index 000000000..41daca871 --- /dev/null +++ b/spec/models/chouette/access_point_spec.rb @@ -0,0 +1,269 @@ +require 'spec_helper' + +describe Chouette::AccessPoint, :type => :model do + +  describe '#objectid' do +    subject { super().objectid } +    it { is_expected.to be_kind_of(Chouette::ObjectId) } +  end + +  it { is_expected.to validate_presence_of :name } +  it { is_expected.to validate_numericality_of :latitude } +  it { is_expected.to validate_numericality_of :longitude } +   +  describe ".latitude" do +    it "should accept -90 value" do +      subject = create :access_point +      subject.latitude = -90 +      expect(subject.valid?).to be_truthy +    end +    it "should reject < -90 value" do +      subject = create :access_point +      subject.latitude = -90.0001 +      expect(subject.valid?).to be_falsey +    end +    it "should accept 90 value" do +      subject = create :access_point +      subject.latitude = 90 +      expect(subject.valid?).to be_truthy +    end +    it "should reject > 90 value" do +      subject = create :access_point +      subject.latitude = 90.0001 +      expect(subject.valid?).to be_falsey +    end +  end + +  describe ".longitude" do +    it "should accept -180 value" do +      subject = create :access_point +      subject.longitude = -180 +      expect(subject.valid?).to be_truthy +    end +    it "should reject < -180 value" do +      subject = create :access_point +      subject.longitude = -180.0001 +      expect(subject.valid?).to be_falsey +    end +    it "should accept 180 value" do +      subject = create :access_point +      subject.longitude = 180 +      expect(subject.valid?).to be_truthy +    end +    it "should reject > 180 value" do +      subject = create :access_point +      subject.longitude = 180.0001 +      expect(subject.valid?).to be_falsey +    end +  end + +  describe ".long_lat" do +    it "should accept longitude and latitude both as nil" do +      subject = create :access_point +      subject.longitude = nil +      subject.latitude = nil +      expect(subject.valid?).to be_truthy +    end +    it "should accept longitude and latitude both numerical" do +      subject = create :access_point +      subject.longitude = 10 +      subject.latitude = 10 +      expect(subject.valid?).to be_truthy +    end +    it "should reject longitude nil with latitude numerical" do +      subject = create :access_point +      subject.longitude = nil +      subject.latitude = 10 +      expect(subject.valid?).to be_falsey +    end +    it "should reject longitude numerical with latitude nil" do +      subject = create :access_point +      subject.longitude = 10 +      subject.latitude = nil +      expect(subject.valid?).to be_falsey +    end +  end  + +  describe "#access_type" do +    def self.legacy_access_types +      %w{In Out InOut} +    end +     +    legacy_access_types.each do |access_type| +      context "when access_type is #{access_type}" do +        access_point_type = Chouette::AccessPointType.new(access_type.underscore) +        it "should be #{access_point_type}" do +          subject.access_type = access_type +          expect(subject.access_point_type).to eq(access_point_type) +        end +      end +    end +  end + +  describe "#access_point_type=" do     +    it "should change access_type with Chouette::AccessPointType#name" do +      subject.access_point_type = "in_out" +      expect(subject.access_type).to eq("InOut") +    end + +  end + +  describe "#to_lat_lng" do +     +    it "should return nil if latitude is nil" do +      subject.latitude = nil +      expect(subject.to_lat_lng).to be_nil +    end + +    it "should return nil if longitude is nil" do +      subject.longitude = nil +      expect(subject.to_lat_lng).to be_nil +    end + +  end + +  describe "#geometry" do +     +    it "should be nil when to_lat_lng is nil" do +      allow(subject).to receive_messages :to_lat_lng => nil +      expect(subject.geometry).to be_nil +    end + +  end + +  describe "#generic_access_link_matrix" do +    it "should have 2 generic_access_links in matrix" do +      stop_place = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => stop_place +      subject = create :access_point, :stop_area => stop_place +      expect(subject.generic_access_link_matrix.size).to eq(2) +    end +     +    it "should have new generic_access_links in matrix" do +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +      subject = create :access_point, :stop_area => commercial_stop_point +      subject.generic_access_link_matrix.each do |link| +        expect(link.id).to be_nil +      end +    end +    it "should have only last generic_access_links as new in matrix" do +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +      subject = create :access_point, :stop_area => commercial_stop_point +      link = create :access_link, :access_point => subject, :stop_area => commercial_stop_point +      subject.generic_access_link_matrix.each do |link| +        if link.link_key.start_with?"A_"  +          expect(link.id).not_to be_nil +        else +          expect(link.id).to be_nil +        end   +      end +    end +  end + +  describe "#detail_access_link_matrix" do +    it "should have 4 detail_access_links in matrix" do +      stop_place = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => stop_place +      quay1 = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      quay2 = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      subject = create :access_point, :stop_area => stop_place +      expect(subject.detail_access_link_matrix.size).to eq(4) +    end +     +    it "should have new detail_access_links in matrix" do +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +      quay = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      subject = create :access_point, :stop_area => commercial_stop_point +      subject.detail_access_link_matrix.each do |link| +        expect(link.id).to be_nil +      end +    end +    it "should have only last detail_access_links as new in matrix" do +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +      quay = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      subject = create :access_point, :stop_area => commercial_stop_point +      link = create :access_link, :access_point => subject, :stop_area => quay +      subject.detail_access_link_matrix.each do |link| +        if link.link_key.start_with?"A_"  +          expect(link.id).not_to be_nil +        else +          expect(link.id).to be_nil +        end   +      end +    end +  end + +  describe "#coordinates" do +    it "should convert coordinates into latitude/longitude" do +     commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +     subject = create :access_point, :stop_area => commercial_stop_point, :coordinates => "45.123,120.456" +     expect(subject.longitude).to be_within(0.001).of(120.456) +     expect(subject.latitude).to be_within(0.001).of(45.123) +   end +    it "should set empty coordinates into nil latitude/longitude" do +     commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +     subject = create :access_point, :stop_area => commercial_stop_point, :coordinates => "45.123,120.456" +     expect(subject.longitude).to be_within(0.001).of(120.456) +     expect(subject.latitude).to be_within(0.001).of(45.123) +     subject.coordinates = "" +     subject.save +     expect(subject.longitude).to be_nil +     expect(subject.latitude).to be_nil +   end +    it "should convert latitude/longitude into coordinates" do +     commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +     subject = create :access_point, :stop_area => commercial_stop_point, :longitude => 120.456, :latitude => 45.123 +     expect(subject.coordinates).to eq("45.123,120.456") +   end +    it "should convert nil latitude/longitude into empty coordinates" do +    commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +     subject = create :access_point, :stop_area => commercial_stop_point, :longitude => nil, :latitude => nil +     expect(subject.coordinates).to eq("") +   end +    it "should accept valid coordinates" do +     commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +     subject = create :access_point, :stop_area => commercial_stop_point, :coordinates => "45.123,120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "45.123, 120.456" +     expect(subject.valid?).to be_truthy +     expect(subject.longitude).to be_within(0.001).of(120.456) +     expect(subject.latitude).to be_within(0.001).of(45.123) +     subject.coordinates = "45.123,  -120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "45.123 ,120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "45.123   ,   120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = " 45.123,120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "45.123,120.456  " +     expect(subject.valid?).to be_truthy +    end +    it "should accept valid coordinates on limits" do +     commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +     subject = create :access_point, :stop_area => commercial_stop_point, :coordinates => "90,180" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "-90,-180" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "-90.,180." +     expect(subject.valid?).to be_truthy +     subject.coordinates = "-90.0,180.00" +     expect(subject.valid?).to be_truthy +    end +    it "should reject invalid coordinates" do +     commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint"  +     subject = create :access_point, :stop_area => commercial_stop_point +     subject.coordinates = ",12" +     expect(subject.valid?).to be_falsey +     subject.coordinates = "-90" +     expect(subject.valid?).to be_falsey +     subject.coordinates = "-90.1,180." +     expect(subject.valid?).to be_falsey +     subject.coordinates = "-90.0,180.1" +     expect(subject.valid?).to be_falsey +     subject.coordinates = "-91.0,18.1" +     expect(subject.valid?).to be_falsey +    end +  end +   +end diff --git a/spec/models/chouette/active_record_spec.rb b/spec/models/chouette/active_record_spec.rb new file mode 100644 index 000000000..087e41576 --- /dev/null +++ b/spec/models/chouette/active_record_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +describe Chouette::ActiveRecord, :type => :model do + +  it { expect(Chouette::ActiveRecord.ancestors).to include(ActiveRecord::Base) } + +  describe "table_name" do + +    it "should return line for Chouette::Line" do +      expect(Chouette::Line.table_name).to eq("lines") +    end +     +    it "should return ptnetwork for Chouette::Network" do +      expect(Chouette::Network.table_name).to eq("networks") +    end + +    it "should return timetable_date for Chouette::TimeTableDate" do +      expect(Chouette::TimeTableDate.table_name).to eq("time_table_dates") +    end + +    it "should return timetable_period for Chouette::TimeTablePeriod" do +      expect(Chouette::TimeTablePeriod.table_name).to eq("time_table_periods") +    end + +  end + +  describe "method_missing" do +     +    it "should support method with additionnal underscores" do +      stop_area = Chouette::StopArea.new +      expect(stop_area.area_type).to eq(stop_area.area_type) +    end + +  end + + +  describe "respond_to?" do +     +    it "should respond to method with additionnal underscores" do +      stop_area = Chouette::StopArea.new +      expect(stop_area.respond_to?(:area_type)).to be_truthy +    end + +  end + +#   describe "create_reflection" do + +#     let(:macro) { :has_many } +#     let(:name) { :lines } +#     let(:options) { {} } +#     let(:active_record) { Chouette::Network } +     +#     let(:modified_options) { {:modified => true}  } + +#     it "should invoke create_reflection_without_chouette_naming with modified options" do +#       allow(Chouette::ActiveRecord::Reflection).to receive_messages :new => double(:options_with_default => modified_options) +#       expect(Chouette::ActiveRecord).to receive(:create_reflection_without_chouette_naming).with macro, name, modified_options, active_record + +#       Chouette::ActiveRecord.create_reflection macro, name, options, active_record +#     end + +#   end + +end + +# describe Chouette::ActiveRecord::Reflection, :type => :model do + +#   let(:macro) { :has_many } +#   let(:name) { :lines } +#   let(:options) { {} } +#   let(:active_record) { Chouette::Network } + +#   subject { Chouette::ActiveRecord::Reflection.new macro, name, options, active_record } + +#   describe "collection?" do + +#     it "should be true when macro is has_many" do +#       allow(subject).to receive_messages :macro => :has_many +#       expect(subject).to be_collection +#     end + +#     it "should be false when macro is belongs_to" do +#       allow(subject).to receive_messages :macro => :belong_to +#       expect(subject).not_to be_collection +#     end + +#   end + +#   describe "class_name" do +     +#     it "should be Chouette::Line when name is line" do +#       allow(subject).to receive_messages :name => "line" +#       expect(subject.class_name).to eq("Chouette::Line") +#     end + +#     it "should be Chouette::Routes when name is routes and reflection is a collection" do +#       allow(subject).to receive_messages :name => "routes", :collection? => true +#       expect(subject.class_name).to eq("Chouette::Route") +#     end + +#   end + + +#   describe "options" do +     +#     it "should define class_name if not" do +#       allow(subject).to receive_messages :options => {}, :class_name => "class_name" +#       expect(subject.options_with_default[:class_name]).to eq("class_name") +#     end + +#     it "should not define class_name if presents" do +#       allow(subject).to receive_messages :options => {:class_name => "dummy"} +#       expect(subject.options_with_default[:class_name]).to eq("dummy") +#     end + +#   end +   + +# end + + diff --git a/spec/models/chouette/area_type_spec.rb b/spec/models/chouette/area_type_spec.rb new file mode 100644 index 000000000..14902416b --- /dev/null +++ b/spec/models/chouette/area_type_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Chouette::AreaType, :type => :model do +   +  def mode(text_code = "test", numerical_code = nil) +    numerical_code ||= 1 if text_code == "test" +    Chouette::AreaType.new(text_code, numerical_code) +  end + +  describe "#to_i" do +     +    it "should return numerical code" do +      expect(mode("test", 1).to_i).to eq(1) +    end + +  end + +  it "should return true to #test? when text code is 'test'" do +    expect(mode("test")).to be_test +  end + +  it "should be equal when text codes are identical" do +    expect(mode("test",1)).to eq(mode("test", 2)) +  end + +  describe ".new" do + +    it "should find numerical code from text code" do +      expect(mode("boarding_position").to_i).to eq(0) +    end + +    it "should find text code from numerical code" do +      expect(mode(0)).to eq("boarding_position") +    end + +    it "should accept another mode" do +      expect(Chouette::AreaType.new(mode("test"))).to eq(mode("test")) +    end +     +  end + + +  describe ".all" do +     +    Chouette::AreaType.definitions.each do |text_code, numerical_code| +      it "should include a AreaType #{text_code}" do +        expect(Chouette::AreaType.all).to include(Chouette::AreaType.new(text_code)) +      end +    end + +  end + +end diff --git a/spec/models/chouette/company_spec.rb b/spec/models/chouette/company_spec.rb new file mode 100644 index 000000000..3da8b4311 --- /dev/null +++ b/spec/models/chouette/company_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Chouette::Company, :type => :model do + +  subject { create(:company) } + +  it { is_expected.to validate_presence_of :name } + +  # it { should validate_presence_of :objectid } +  it { is_expected.to validate_uniqueness_of :objectid } + +  describe "#nullables empty" do +    it "should set null empty nullable attributes" do +      subject.organizational_unit = '' +      subject.operating_department_name = '' +      subject.code = '' +      subject.phone = '' +      subject.fax = '' +      subject.email = '' +      subject.nil_if_blank +      expect(subject.organizational_unit).to be_nil +      expect(subject.operating_department_name).to be_nil +      expect(subject.code).to be_nil +      expect(subject.phone).to be_nil +      expect(subject.fax).to be_nil +      expect(subject.email).to be_nil +    end +  end + +  describe "#nullables non empty" do +    it "should not set null non epmty nullable attributes" do +      subject.organizational_unit = 'a' +      subject.operating_department_name = 'b' +      subject.code = 'c' +      subject.phone = 'd' +      subject.fax = 'z' +      subject.email = 'r' +      subject.nil_if_blank +      expect(subject.organizational_unit).not_to be_nil +      expect(subject.operating_department_name).not_to be_nil +      expect(subject.code).not_to be_nil +      expect(subject.phone).not_to be_nil +      expect(subject.fax).not_to be_nil +      expect(subject.email).not_to be_nil +    end +  end + +end diff --git a/spec/models/chouette/connection_link_spec.rb b/spec/models/chouette/connection_link_spec.rb new file mode 100644 index 000000000..e76190bcf --- /dev/null +++ b/spec/models/chouette/connection_link_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe Chouette::ConnectionLink, :type => :model do +  let!(:quay) { create :stop_area, :area_type => "Quay" } +  let!(:boarding_position) { create :stop_area, :area_type => "BoardingPosition" } +  let!(:commercial_stop_point) { create :stop_area, :area_type => "CommercialStopPoint" } +  let!(:stop_place) { create :stop_area, :area_type => "StopPlace" } +  let!(:itl) { create :stop_area, :area_type => "ITL" } +  subject { create(:connection_link) } + +  it { is_expected.to validate_uniqueness_of :objectid } + +  describe '#objectid' do +    subject { super().objectid } +    it { is_expected.to be_kind_of(Chouette::ObjectId) } +  end + +  it { is_expected.to validate_presence_of :name } + +  describe "#connection_link_type" do + +    def self.legacy_link_types +      %w{Underground Mixed Overground} +    end +     +    legacy_link_types.each do |link_type| +      context "when link_type is #{link_type}" do +        connection_link_type = Chouette::ConnectionLinkType.new(link_type.underscore) +        it "should be #{connection_link_type}" do +          subject.link_type = link_type +          expect(subject.connection_link_type).to eq(connection_link_type) +        end +      end +    end +    context "when link_type is nil" do +      it "should be nil" do +        subject.link_type = nil +        expect(subject.connection_link_type).to be_nil +      end +    end + +  end + +  describe "#connection_link_type=" do +     +    it "should change link_type with ConnectionLinkType#name" do +      subject.connection_link_type = "Test" +      expect(subject.link_type).to eq("Test") +    end + +  end + +  describe ".possible_areas" do + +    it "should not find areas type ITL" do +      expect(subject.possible_areas).not_to eq([itl])  +    end +  end + +end diff --git a/spec/models/chouette/direction_spec.rb b/spec/models/chouette/direction_spec.rb new file mode 100644 index 000000000..8075a509e --- /dev/null +++ b/spec/models/chouette/direction_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe Chouette::Direction, :type => :model do + +  describe ".new" do +    context "when single argument provided is a direction" do +      let(:text) { "dummy"} +      let(:direction){ Chouette::Direction.new( text, 1)} +      it "should be equals to the provided direction" do +        expect(direction).to eq(Chouette::Direction.new( direction)) +      end +    end +  end + +  shared_examples_for "west direction" do +    it "should return true to #west? " do +      expect(direction).to be_west +    end +    context "#to_i" do +      it "should return 6" do +        expect(direction.to_i).to eq(6) +      end +    end +  end + +  context "when instanciating with existing text only ('west' for example)" do +    let(:direction){ Chouette::Direction.new "west"} +    it_should_behave_like "west direction" +  end +  context "when instanciating with existing numerical code only (6 for example)" do +    let(:direction){ Chouette::Direction.new 6} +    it_should_behave_like "west direction" +  end + +  context "when instanciating with 'dummy' and 1 as argumrent" do +    let(:text) { "dummy"} +    let(:number) { 1} +    let(:direction){ Chouette::Direction.new( text, number)} + +    it "should return true to #dummy? " do +      expect(direction.send( "#{text}?".to_sym)).to be_truthy +    end + +    it "should return false to #other-dummy? " do +      expect(direction.send( "other-#{text}?".to_sym)).to be_falsey +    end + +    context "#to_i" do +      it "should return provided number" do +        expect(direction.to_i).to eq(number) +      end +    end + +    context "#name" do +      it "should return provided text" do +        expect(direction.name).to eq(text) +      end +    end +  end +end diff --git a/spec/models/chouette/exporter_spec.rb b/spec/models/chouette/exporter_spec.rb new file mode 100644 index 000000000..8bcd14761 --- /dev/null +++ b/spec/models/chouette/exporter_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Chouette::Exporter, :type => :model do + +  subject { Chouette::Exporter.new("test") } + +  describe "#export" do + +    let(:chouette_command) { double :run! => true } + +    before(:each) do +      allow(subject).to receive_messages :chouette_command => chouette_command +    end + +    it "should use specified file in -outputFile option" do +      expect(chouette_command).to receive(:run!).with(hash_including(:output_file => File.expand_path('file'))) +      subject.export "file" +    end +     +    it "should use specified format in -format option" do +      expect(chouette_command).to receive(:run!).with(hash_including(:format => 'DUMMY')) +      subject.export "file", :format => "dummy" +    end +     +  end + +end + diff --git a/spec/models/chouette/file_validator_spec.rb b/spec/models/chouette/file_validator_spec.rb new file mode 100644 index 000000000..d9b29fa21 --- /dev/null +++ b/spec/models/chouette/file_validator_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Chouette::FileValidator, :type => :model do + +  subject { Chouette::FileValidator.new("public") } + +  before(:each) do +    allow(subject).to receive_messages :execute! => true +  end + + +  describe "#validate" do + +    let(:chouette_command) { double :run! => true } + +    before(:each) do +      allow(subject).to receive_messages :chouette_command => chouette_command +    end + +    it "should use specified file in -inputFile option" do +      expect(chouette_command).to receive(:run!).with(hash_including(:input_file => File.expand_path('file'))) +      subject.validate "file" +    end +         +  end + +end + diff --git a/spec/models/chouette/footnote_spec.rb b/spec/models/chouette/footnote_spec.rb new file mode 100644 index 000000000..5c09e3931 --- /dev/null +++ b/spec/models/chouette/footnote_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe Chouette::Footnote do + +  subject { build(:footnote) } + +  it { should validate_presence_of :line } + +end diff --git a/spec/models/chouette/group_of_line_spec.rb b/spec/models/chouette/group_of_line_spec.rb new file mode 100644 index 000000000..66932cfd6 --- /dev/null +++ b/spec/models/chouette/group_of_line_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Chouette::GroupOfLine, :type => :model do + +  subject { create(:group_of_line) } + +  it { is_expected.to validate_presence_of :name } + +  # it { should validate_presence_of :objectid } +  it { is_expected.to validate_uniqueness_of :objectid } + +  describe "#stop_areas" do +    let!(:line){create(:line, :group_of_lines => [subject])} +    let!(:route){create(:route, :line => line)} +    it "should retreive group of line's stop_areas" do +      expect(subject.stop_areas.count).to eq(route.stop_points.count) +    end +  end +   +  context "#line_tokens=" do +    let!(:line1){create(:line)} +    let!(:line2){create(:line)} + +    it "should return associated line ids" do +      subject.update_attributes :line_tokens => [line1.id, line2.id].join(',') +      expect(subject.lines).to include( line1) +      expect(subject.lines).to include( line2) +    end +  end + +end diff --git a/spec/models/chouette/journey_frequency_spec.rb b/spec/models/chouette/journey_frequency_spec.rb new file mode 100644 index 000000000..2e2088a0c --- /dev/null +++ b/spec/models/chouette/journey_frequency_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Chouette::JourneyFrequency, type: :model do +  let!(:vehicle_journey) { create(:vehicle_journey_even)} + +  describe '#create' do +    context 'when valid' do +      it 'should be created' do +        journey_frequency = build(:journey_frequency) +        journey_frequency.vehicle_journey_id = vehicle_journey.id +        expect(journey_frequency.save!).to be +      end +    end + +    context 'when first_departure_time not valid' do +      it 'fails validation with first_departure_time before timeband start_time' do +        journey_frequency = build(:journey_frequency_first_departure_time_invalid) +        journey_frequency.vehicle_journey_id = vehicle_journey.id +        expect(journey_frequency).to be_invalid +      end +    end + +    context 'when last_departure_time not valid' do +      it 'fails validation with last_departure_time after timeband end_time' do +        journey_frequency = build(:journey_frequency_last_departure_time_invalid) +        journey_frequency.vehicle_journey_id = vehicle_journey.id +        expect(journey_frequency).to be_invalid +      end +    end + +    context 'when first and last departure_time not valid' do +      it 'fails validation with first_departure_time equal last_departure_time' do +        journey_frequency = build(:journey_frequency_departure_time_invalid) +        journey_frequency.vehicle_journey_id = vehicle_journey.id +        expect(journey_frequency).to be_invalid +      end +    end + +    context 'when scheduled_headway_interval not valid' do +      it 'fails validation with scheduled_headway_interval is not set' do +        journey_frequency = build(:journey_frequency_scheduled_headway_interval_invalid) +        journey_frequency.vehicle_journey_id = vehicle_journey.id +        expect(journey_frequency).to be_invalid +      end +    end +  end +end diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb new file mode 100644 index 000000000..68c221c9a --- /dev/null +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe Chouette::JourneyPattern, :type => :model do +  describe "#stop_point_ids" do +    context "for a journey_pattern using only route's stop on odd position" do +      let!(:journey_pattern){ create( :journey_pattern_odd)} +      let!(:vehicle_journey){ create( :vehicle_journey_odd, :journey_pattern => journey_pattern)} +       +      # workaroud +      #subject { journey_pattern} +      subject { Chouette::JourneyPattern.find(vehicle_journey.journey_pattern_id)} + +      context "when a all route's stop have been removed from journey_pattern" do +        before(:each) do +          subject.stop_point_ids = [] +        end +        it "should remove all vehicle_journey_at_stop" do +          vjas_stop_ids = Chouette::VehicleJourney.find(vehicle_journey.id).vehicle_journey_at_stops +          expect(vjas_stop_ids.count).to eq(0) +        end +        it "should keep departure and arrival shortcut up to date to nil" do +          expect(subject.arrival_stop_point_id).to be_nil +          expect(subject.departure_stop_point_id).to be_nil +        end +      end +       +      context "when a route's stop has been removed from journey_pattern" do +        let!(:last_stop_id){ subject.stop_point_ids.last} +        before(:each) do +          subject.stop_point_ids = subject.stop_point_ids - [last_stop_id] +        end +        it "should remove vehicle_journey_at_stop for last stop" do +          vjas_stop_ids = Chouette::VehicleJourney.find(vehicle_journey.id).vehicle_journey_at_stops.map(&:stop_point_id) +          expect(vjas_stop_ids.count).to eq(subject.stop_point_ids.size) +          expect(vjas_stop_ids).not_to include( last_stop_id) +        end +        it "should keep departure and arrival shortcut up to date" do +          ordered = subject.stop_points.sort { |a,b| a.position <=> b.position} + +          expect(subject.arrival_stop_point_id).to eq(ordered.last.id) +          expect(subject.departure_stop_point_id).to eq(ordered.first.id) +        end +      end +       +      context "when a route's stop has been added in journey_pattern" do +        let!(:new_stop){ subject.route.stop_points[1]} +        before(:each) do +          subject.stop_point_ids = subject.stop_point_ids + [new_stop.id] +        end +        it "should add a new vehicle_journey_at_stop for that stop" do +          vjas_stop_ids = Chouette::VehicleJourney.find(vehicle_journey.id).vehicle_journey_at_stops.map(&:stop_point_id) +          expect(vjas_stop_ids.count).to eq(subject.stop_point_ids.size) +          expect(vjas_stop_ids).to include( new_stop.id) +        end +        it "should keep departure and arrival shortcut up to date" do +          ordered = subject.stop_points.sort { |a,b| a.position <=> b.position} + +          expect(subject.arrival_stop_point_id).to eq(ordered.last.id) +          expect(subject.departure_stop_point_id).to eq(ordered.first.id) +        end +      end +    end +  end +end diff --git a/spec/models/chouette/line_spec.rb b/spec/models/chouette/line_spec.rb new file mode 100644 index 000000000..f0a2453b5 --- /dev/null +++ b/spec/models/chouette/line_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +describe Chouette::Line, :type => :model do + +  subject { create(:line) } + +  it { is_expected.to validate_presence_of :network } +  it { is_expected.to validate_presence_of :company } + +  it { is_expected.to validate_presence_of :name } + +  # it { should validate_presence_of :objectid } +  it { is_expected.to validate_uniqueness_of :objectid } + +  describe '#objectid' do +    subject { super().objectid } +    it { is_expected.to be_kind_of(Chouette::ObjectId) } +  end + +  # it { should validate_numericality_of :objectversion } + +  describe ".last_stop_areas_parents" do + +    it "should return stop areas if no parents" do +      line = create(:line_with_stop_areas) +      expect(line.stop_areas_last_parents).to eq(line.stop_areas) +    end + +    it "should return stop areas parents if parents" do +      line = create(:line_with_stop_areas) +      route = create(:route, :line => line) +      parent = create(:stop_area) +      stop_areas = [ create(:stop_area),  create(:stop_area), create(:stop_area, :parent_id => parent.id) ] +      stop_areas.each do |stop_area| +        create(:stop_point, :stop_area => stop_area, :route => route) +      end + +      expect(line.stop_areas_last_parents).to match(line.stop_areas[0..(line.stop_areas.size - 2)].push(parent)) +    end + +  end + +  describe "#stop_areas" do +    let!(:route){create(:route, :line => subject)} +    it "should retreive route's stop_areas" do +      expect(subject.stop_areas.count).to eq(route.stop_points.count) +    end +  end + +  describe "#transport_mode" do + +    def self.legacy_transport_mode_names +      %w{Air Train LongDistanceTrain LocalTrain RapidTransit Metro Tramway Coach Bus Ferry Waterborne PrivateVehicle Walk Trolleybus Bicycle Shuttle Taxi VAL Other} +    end + +    legacy_transport_mode_names.each do |transport_mode_name| +      context "when transport_mode_name is #{transport_mode_name}" do +        transport_mode = Chouette::TransportMode.new(transport_mode_name.underscore) +        it "should be #{transport_mode}" do +          subject.transport_mode_name = transport_mode_name +          expect(subject.transport_mode).to eq(transport_mode) +        end +      end +    end +    context "when transport_mode_name is nil" do +      it "should be nil" do +        subject.transport_mode_name = nil +        expect(subject.transport_mode).to be_nil +      end +    end + +  end + +  describe "#transport_mode=" do + +    it "should change transport_mode_name with TransportMode#name" do +      subject.transport_mode = "Test" +      expect(subject.transport_mode_name).to eq("Test") +    end + +  end + +  describe ".transport_modes" do + +    it "should not include unknown transport_mode" do +      expect(Chouette::Line.transport_modes).not_to include(Chouette::TransportMode.new("unknown")) +    end + +    it "should not include interchange transport_mode" do +      expect(Chouette::Line.transport_modes).not_to include(Chouette::TransportMode.new("interchange")) +    end + +  end + +  context "#group_of_line_tokens=" do +    let!(:group_of_line1){create(:group_of_line)} +    let!(:group_of_line2){create(:group_of_line)} + +    it "should return associated group_of_line ids" do +      subject.update_attributes :group_of_line_tokens => [group_of_line1.id, group_of_line2.id].join(',') +      expect(subject.group_of_lines).to include( group_of_line1) +      expect(subject.group_of_lines).to include( group_of_line2) +    end +  end + +  describe "#update_attributes footnotes_attributes" do +    context "instanciate 2 footnotes without line" do +      let!( :footnote_first) {build( :footnote, :line_id => nil)} +      let!( :footnote_second) {build( :footnote, :line_id => nil)} +      it "should add 2 footnotes to the line" do +        subject.update_attributes :footnotes_attributes => +          { Time.now.to_i => footnote_first.attributes, +            (Time.now.to_i-5) => footnote_second.attributes} +        expect(Chouette::Line.find( subject.id ).footnotes.size).to eq(2) +      end +    end +  end + + +end diff --git a/spec/models/chouette/loader_spec.rb b/spec/models/chouette/loader_spec.rb new file mode 100644 index 000000000..d359c443e --- /dev/null +++ b/spec/models/chouette/loader_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Chouette::Loader, :type => :model do + +  subject { Chouette::Loader.new("test") } + +  before(:each) do +    allow(subject).to receive_messages :execute! => true +  end + +  describe "#load_dump" do + +  end + +  describe "#import" do + +    let(:chouette_command) { double :run! => true } + +    before(:each) do +      allow(subject).to receive_messages :chouette_command => chouette_command +    end + +    it "should use specified file in -inputFile option" do +      expect(chouette_command).to receive(:run!).with(hash_including(:input_file => File.expand_path('file'))) +      subject.import "file" +    end +     +    it "should use specified format in -format option" do +      expect(chouette_command).to receive(:run!).with(hash_including(:format => 'DUMMY')) +      subject.import "file", :format => "dummy" +    end +     +  end + +  describe "#create" do +     +    it "should quote schema name" do +      expect(subject).to receive(:execute!).with(/"test"/) +      subject.create +    end + +  end + +  describe "#drop" do + +    it "should quote schema name" do +      expect(subject).to receive(:execute!).with(/"test"/) +      subject.drop +    end +     +  end + +  describe "#backup" do + +    let(:file) { "/dev/null" } + +    it "should call pg_dump" do +      expect(subject).to receive(:execute!).with(/^pg_dump/) +      subject.backup file +    end + +    it "should dump in specified file" do +      expect(subject).to receive(:execute!).with(/-f #{file}/) +      subject.backup file +    end +  end + +end + diff --git a/spec/models/chouette/network_spec.rb b/spec/models/chouette/network_spec.rb new file mode 100644 index 000000000..c9e510e84 --- /dev/null +++ b/spec/models/chouette/network_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Chouette::Network, :type => :model do + +  subject { create(:network) } + +  it { is_expected.to validate_presence_of :name } + +  # it { should validate_presence_of :objectid } +  it { is_expected.to validate_uniqueness_of :objectid } + +  describe "#stop_areas" do +    let!(:line){create(:line, :network => subject)} +    let!(:route){create(:route, :line => line)} +    it "should retrieve route's stop_areas" do +      expect(subject.stop_areas.count).to eq(route.stop_points.count) +    end +  end +end diff --git a/spec/models/chouette/object_id_spec.rb b/spec/models/chouette/object_id_spec.rb new file mode 100644 index 000000000..dd8b66388 --- /dev/null +++ b/spec/models/chouette/object_id_spec.rb @@ -0,0 +1,149 @@ +require 'spec_helper' + +describe Chouette::ObjectId, :type => :model do + +  def objectid(value = "abc:StopArea:abc123") +    Chouette::ObjectId.new value +  end + +  subject { objectid } + +  context "when invalid" do + +    subject { objectid("abc") } + +    it { is_expected.not_to be_valid } + +    describe '#parts' do +      subject { super().parts } +      it { is_expected.to be_nil } +    end + +    describe '#system_id' do +      subject { super().system_id } +      it { is_expected.to be_nil } +    end + +  end +   +  context "when with spaces in last part" do + +    subject { objectid("abc:Line:Aze toto") } + +    it { is_expected.not_to be_valid } + + +  end +   +  context "when with spaces in first part" do + +    subject { objectid("ae abc:Line:Aze") } + +    it { is_expected.not_to be_valid } + + +  end +   +  context "when with spaces in middle part" do + +    subject { objectid("aeabc:Li ne:Aze") } + +    it { is_expected.not_to be_valid } +     + +  end +   +  context "when invalid in first part" do + +    subject { objectid("Abc_+19:Line:Abc") } + +    it { is_expected.not_to be_valid } +  end +   +  context "when invalid in middle part" do + +    subject { objectid("Abc_19:Li56ne:Abc") } + +    it { is_expected.not_to be_valid } +  end + +  context "when invalid in last part" do + +    subject { objectid("Abc_19:Line:Ab+c") } + +    it { is_expected.not_to be_valid } +  end +  context "when valid" do + +    subject { objectid("Abc_19:Line:Abc_12-") } + +    it { is_expected.to be_valid } +  end + +  describe "#parts" do + +    it "should be the 3 parts of the ObjectId" do +      expect(objectid("abc:StopArea:abc123").parts).to eq(%w{abc StopArea abc123}) +    end + +  end + +  describe "#system_id" do +     +    it "should be the first ObjectId parts" do +      expect(objectid("first:second:third").system_id).to eq("first") +    end + +  end + +  describe "#object_type" do +     +    it "should be the second ObjectId parts" do +      expect(objectid("first:second:third").object_type).to eq("second") +    end + +  end + +  describe "#local_id" do +     +    it "should be the third ObjectId parts" do +      expect(objectid("first:second:third").local_id).to eq("third") +    end + +  end + +  it "should be valid when parts are found" do +    allow(subject).to receive_messages :parts => "dummy" +    expect(subject).to be_valid +  end + +  describe ".create" do + +    let(:given_system_id) { "systemId" } +    let(:given_object_type) { "objectType" } +    let(:given_local_id) { "localId" } + +    subject { Chouette::ObjectId.create(given_system_id, given_object_type, given_local_id) } + +    it "should return ObjectId attributes" do +      expect(subject.send(:system_id)).to eq(given_system_id) +      expect(subject.send(:object_type)).to eq(given_object_type) +      expect(subject.send(:local_id)).to eq(given_local_id) +    end + +  end + +  describe ".new" do +     +    it "should return an existing ObjectId" do +      expect(Chouette::ObjectId.new(objectid)).to eq(objectid) +    end + +    it "should create an empty ObjectId with nil" do +      expect(Chouette::ObjectId.new(nil)).to be_empty +    end + +  end + + +end diff --git a/spec/models/chouette/route_section_spec.rb b/spec/models/chouette/route_section_spec.rb new file mode 100644 index 000000000..f064d38ea --- /dev/null +++ b/spec/models/chouette/route_section_spec.rb @@ -0,0 +1,93 @@ +# require 'spec_helper' +# +# RSpec.describe Chouette::RouteSection, :type => :model do +# +#   subject { create :route_section } +# +#   it { should validate_presence_of(:departure) } +#   it { should validate_presence_of(:arrival) } +# +#   describe "#default_geometry" do +# +#     it "should return nil when departure isn't defined" do +#       subject.departure  = nil +#       expect(subject.default_geometry).to be_nil +#     end +# +#     it "should return nil when arrival isn't defined" do +#       subject.arrival  = nil +#       expect(subject.default_geometry).to be_nil +#     end +#  +#     it "should return nil when departure has no geometry" do +#       subject.departure.stub :geometry +#       expect(subject.default_geometry).to be_nil +#     end +# +#     it "should return nil when arrival has no geometry" do +#       subject.arrival.stub :geometry +#       expect(subject.default_geometry).to be_nil +#     end +# +#     it "should use departure geometry as first point" do +#       expect(subject.default_geometry.first).to eq(subject.departure.geometry) +#     end +# +#   end +# +#   describe "#process_geometry" do +# +#     let(:sample_geometry) { line_string("0 0,1 1").to_rgeo } +# +#     context "without processor" do +# +#       it "should use the input geometry" do +#         subject.input_geometry = sample_geometry +#         subject.process_geometry +#         expect(subject.processed_geometry).to eq(subject.input_geometry) +#       end +# +#       it "should use the default geometry when no input is defined" do +#         subject.input_geometry = nil +#         subject.process_geometry +#         expect(subject.processed_geometry).to eq(subject.default_geometry.to_rgeo) +#       end +# +#     end +# +#     # context "with a processor" do +#     # +#     #   it "should use the processor result" do +#     #     subject.processor = Proc.new { |s| sample_geometry } +#     #     subject.process_geometry +#     #     subject.processor = nil +#     #     expect(subject.processed_geometry).to eq(sample_geometry) +#     #   end +#     # end +#   end +# +#   describe "#distance" do +# +#     context "with simple line" do +#       let(:sample_geometry) { line_string("2.329534 48.842397,2.325725 48.855839").to_rgeo } +#       it "should return the right distance" do +#         subject.input_geometry = sample_geometry +#         subject.process_geometry +#         expect(subject.distance).to eq(sample_geometry.to_georuby.spherical_distance) +#       end +#     end +# +#     context "with complex line" do +#       let(:sample_geometry) { line_string("2.329561 48.842397, 2.329351 48.843119, 2.329152 48.843801, 2.3289820000000003 48.844426,2.3287960000000005 48.845059,2.3286540000000007 48.845575,2.3283130000000005 48.846748,2.3281220000000005 48.847404999999995,2.3279330000000003 48.848088,2.3278860000000003 48.848245999999996,2.3273240000000004 48.850142999999996,2.3273030000000006 48.850218999999996,2.3271630000000005 48.850745999999994,2.3270140000000006 48.85130999999999,2.3269350000000006 48.85142799999999,2.3268640000000005 48.85153599999999,2.3268290000000005 48.85161099999999,2.3267490000000004 48.85180999999999,2.3267700000000002 48.852053999999995,2.326759 48.852216999999996,2.326687 48.852427999999996,2.3266620000000002 48.852512,2.3264280000000004 48.853286,2.3264050000000003 48.853362,2.3263710000000004 48.853483,2.326125 48.854343,2.3259980000000002 48.854727,2.325737 48.855833999999994").to_rgeo } +#       it "should return the right distance" do +#         subject.input_geometry = sample_geometry +#         subject.process_geometry +#         expect(subject.distance).to eq(sample_geometry.to_georuby.spherical_distance) +#       end +#     end +# +#   end +# +# +# +# end diff --git a/spec/models/chouette/route_spec.rb b/spec/models/chouette/route_spec.rb new file mode 100644 index 000000000..1acc5a0f7 --- /dev/null +++ b/spec/models/chouette/route_spec.rb @@ -0,0 +1,238 @@ +require 'spec_helper' + +describe Chouette::Route, :type => :model do +  subject { create(:route) } + +  it { is_expected.to validate_uniqueness_of :objectid } + +  describe '#objectid' do +    subject { super().objectid } +    it { is_expected.to be_kind_of(Chouette::ObjectId) } +  end + +  #it { is_expected.to validate_presence_of :name } +  it { is_expected.to validate_presence_of :line } +  #it { is_expected.to validate_presence_of :wayback_code } +  #it { is_expected.to validate_presence_of :direction_code } + +  context "reordering methods" do +    let( :bad_stop_point_ids){subject.stop_points.map { |sp| sp.id + 1}} +    let( :ident){subject.stop_points.map(&:id)} +    let( :first_last_swap){ [ident.last] + ident[1..-2] + [ident.first]} + +    describe "#reorder!" do +      context "invalid stop_point_ids" do +        let( :new_stop_point_ids) { bad_stop_point_ids} +        it { expect(subject.reorder!( new_stop_point_ids)).to be_falsey} +      end + +      context "swaped last and first stop_point_ids" do +        let!( :new_stop_point_ids) { first_last_swap} +        let!( :old_stop_point_ids) { subject.stop_points.map(&:id) } +        let!( :old_stop_area_ids) { subject.stop_areas.map(&:id) } + +        it "should keep stop_point_ids order unchanged" do +          expect(subject.reorder!( new_stop_point_ids)).to be_truthy +          expect(subject.stop_points.map(&:id)).to eq( old_stop_point_ids) +        end +        it "should have changed stop_area_ids order" do +          expect(subject.reorder!( new_stop_point_ids)).to be_truthy +          subject.reload +          expect(subject.stop_areas.map(&:id)).to eq( [old_stop_area_ids.last] + old_stop_area_ids[1..-2] + [old_stop_area_ids.first]) +        end +      end +    end + +    describe "#stop_point_permutation?" do +      context "invalid stop_point_ids" do +        let( :new_stop_point_ids) { bad_stop_point_ids} +        it { is_expected.not_to be_stop_point_permutation( new_stop_point_ids)} +      end +      context "unchanged stop_point_ids" do +        let( :new_stop_point_ids) { ident} +        it { is_expected.to be_stop_point_permutation( new_stop_point_ids)} +      end +      context "swaped last and first stop_point_ids" do +        let( :new_stop_point_ids) { first_last_swap} +        it { is_expected.to be_stop_point_permutation( new_stop_point_ids)} +      end +    end +  end + +  describe "#stop_points_attributes=" do +      let( :journey_pattern) { create( :journey_pattern, :route => subject )} +      let( :vehicle_journey) { create( :vehicle_journey, :journey_pattern => journey_pattern)} +      def subject_stop_points_attributes +          {}.tap do |hash| +              subject.stop_points.each_with_index { |sp,index| hash[ index.to_s ] = sp.attributes } +          end +      end +      context "route having swapped a new stop" do +          let( :new_stop_point ){build( :stop_point, :route => subject)} +          def added_stop_hash +            subject_stop_points_attributes.tap do |h| +                h["4"] = new_stop_point.attributes.merge( "position" => "4", "_destroy" => "" ) +            end +          end +          let!( :new_route_size ){ subject.stop_points.size+1 } + +          it "should have added stop_point in route" do +              subject.update_attributes( :stop_points_attributes => added_stop_hash) +              expect(Chouette::Route.find( subject.id ).stop_points.size).to eq(new_route_size) +          end +          it "should have added stop_point in route's journey pattern" do +              subject.update_attributes( :stop_points_attributes => added_stop_hash) +              expect(Chouette::JourneyPattern.find( journey_pattern.id ).stop_points.size).to eq(new_route_size) +          end +          it "should have added stop_point in route's vehicle journey at stop" do +              subject.update_attributes( :stop_points_attributes => added_stop_hash) +              expect(Chouette::VehicleJourney.find( vehicle_journey.id ).vehicle_journey_at_stops.size).to eq(new_route_size) +          end +      end +      context "route having swapped stop" do +          def swapped_stop_hash +            subject_stop_points_attributes.tap do |h| +                h[ "1" ][ "position" ] = "3" +                h[ "3" ][ "position" ] = "1" +            end +          end +          let!( :new_stop_id_list ){ subject.stop_points.map(&:id).tap {|array| array.insert( 1, array.delete_at(3)); array.insert( 3, array.delete_at(2) )} } + +          it "should have swap stop_points from route" do +              subject.update_attributes( :stop_points_attributes => swapped_stop_hash) +              expect(Chouette::Route.find( subject.id ).stop_points.map(&:id)).to eq(new_stop_id_list) +          end +          it "should have swap stop_points from route's journey pattern" do +              subject.update_attributes( :stop_points_attributes => swapped_stop_hash) +              expect(Chouette::JourneyPattern.find( journey_pattern.id ).stop_points.map(&:id)).to eq(new_stop_id_list) +          end +          it "should have swap stop_points from route's vehicle journey at stop" do +              subject.update_attributes( :stop_points_attributes => swapped_stop_hash) +              expect(Chouette::VehicleJourney.find( vehicle_journey.id ).vehicle_journey_at_stops.map(&:stop_point_id)).to match_array(new_stop_id_list) +          end +      end +      context "route having a deleted stop" do +          def removed_stop_hash +            subject_stop_points_attributes.tap do |h| +                h[ "1" ][ "_destroy" ] = "1" +            end +          end +          let!( :new_stop_id_list ){ subject.stop_points.map(&:id).tap {|array| array.delete_at(1) } } + +          it "should ignore deleted stop_point from route" do +              subject.update_attributes( :stop_points_attributes => removed_stop_hash) +              expect(Chouette::Route.find( subject.id ).stop_points.map(&:id)).to eq(new_stop_id_list) +          end +          it "should ignore deleted stop_point from route's journey pattern" do +              subject.update_attributes( :stop_points_attributes => removed_stop_hash) +              expect(Chouette::JourneyPattern.find( journey_pattern.id ).stop_points.map(&:id)).to eq(new_stop_id_list) +          end +          it "should ignore deleted stop_point from route's vehicle journey at stop" do +              subject.update_attributes( :stop_points_attributes => removed_stop_hash) +              expect(Chouette::VehicleJourney.find( vehicle_journey.id ).vehicle_journey_at_stops.map(&:stop_point_id)).to match_array(new_stop_id_list) +          end +      end +  end + +  describe "#stop_points" do +    context "#find_by_stop_area" do +      context "when arg is first quay id" do +        let(:first_stop_point) { subject.stop_points.first} +        it "should return first quay" do +          expect(subject.stop_points.find_by_stop_area( first_stop_point.stop_area_id)).to eq( first_stop_point) +        end +      end +    end +  end +  describe "#stop_areas" do +    let(:line){ create(:line)} +    let(:route_1){ create(:route, :line => line)} +    let(:route_2){ create(:route, :line => line)} +    it "should retreive all stop_area on route" do +      route_1.stop_areas.each do |sa| +        expect(sa.stop_points.map(&:route_id).uniq).to eq([route_1.id]) +      end +    end + +    context "when route is looping: last and first stop area are the same" do +      it "should retreive same stop_area one last and first position" do +        route_loop = create(:route, :line => line) +        first_stop = Chouette::StopPoint.where( :route_id => route_loop.id, :position => 0).first +        last_stop = create(:stop_point, :route => route_loop, :position => 4, :stop_area => first_stop.stop_area) + +        expect(route_loop.stop_areas.size).to eq(6) +        expect(route_loop.stop_areas.select {|s| s.id == first_stop.stop_area.id}.size).to eq(2) +      end +    end +  end + +  describe "#direction_code" do +    def self.legacy_directions +      %w{A R ClockWise CounterClockWise North NorthWest West SouthWest +        South SouthEast East NorthEast} +    end +    legacy_directions.each do |direction| +      context "when direction is #{direction}" do +        direction_code = Chouette::Direction.new( Chouette::Route.direction_binding[ direction]) +        it "should be #{direction_code}" do +          subject.direction = direction +          expect(subject.direction_code).to eq(direction_code) +        end +      end +    end +    context "when direction is nil" do +      it "should be nil" do +        subject.direction = nil +        expect(subject.direction_code).to be_nil +      end +    end +  end +  describe "#direction_code=" do +    context "when unknown direction is provided" do +      it "should change direction to nil" do +        subject.direction_code = "dummy" +        expect(subject.direction).to be_nil +      end +    end +    context "when an existing direction (west) is provided" do +      it "should change direction Direction.west" do +        subject.direction_code = "west" +        expect(subject.direction).to eq("West") +      end +    end +  end +  describe "#wayback_code" do +    def self.legacy_waybacks +      %w{A R} +    end +    legacy_waybacks.each do |wayback| +      context "when wayback is #{wayback}" do +        wayback_code = Chouette::Wayback.new( Chouette::Route.wayback_binding[ wayback]) +        it "should be #{wayback_code}" do +          subject.wayback = wayback +          expect(subject.wayback_code).to eq(wayback_code) +        end +      end +    end +    context "when wayback is nil" do +      it "should be nil" do +        subject.wayback = nil +        expect(subject.wayback_code).to be_nil +      end +    end +  end +  describe "#wayback_code=" do +    context "when unknown wayback is provided" do +      it "should change wayback to nil" do +        subject.wayback_code = "dummy" +        expect(subject.wayback).to be_nil +      end +    end +    context "when an existing wayback (straight_forward) is provided" do +      it "should change wayback Wayback.straight_forward" do +        subject.wayback_code = "straight_forward" +        expect(subject.wayback).to eq("A") +      end +    end +  end +end diff --git a/spec/models/chouette/stop_area_spec.rb b/spec/models/chouette/stop_area_spec.rb new file mode 100644 index 000000000..b237f08ef --- /dev/null +++ b/spec/models/chouette/stop_area_spec.rb @@ -0,0 +1,443 @@ +require 'spec_helper' + +describe Chouette::StopArea, :type => :model do +  let!(:quay) { create :stop_area, :area_type => "Quay" } +  let!(:boarding_position) { create :stop_area, :area_type => "BoardingPosition" } +  let!(:commercial_stop_point) { create :stop_area, :area_type => "CommercialStopPoint" } +  let!(:stop_place) { create :stop_area, :area_type => "StopPlace" } +  let!(:itl) { create :stop_area, :area_type => "ITL" } + +  describe '#objectid' do +    subject { super().objectid } +    it { is_expected.to be_kind_of(Chouette::ObjectId) } +  end + +  it { is_expected.to validate_presence_of :name } +  it { is_expected.to validate_presence_of :area_type } +  it { is_expected.to validate_numericality_of :latitude } +  it { is_expected.to validate_numericality_of :longitude } + +   +  describe ".latitude" do +    it "should accept -90 value" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.latitude = -90 +      expect(subject.valid?).to be_truthy +    end +    it "should reject < -90 value" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.latitude = -90.0001 +      expect(subject.valid?).to be_falsey +    end +    it "should accept 90 value" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.latitude = 90 +      expect(subject.valid?).to be_truthy +    end +    it "should reject > 90 value" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.latitude = 90.0001 +      expect(subject.valid?).to be_falsey +    end +  end + +  describe ".longitude" do +    it "should accept -180 value" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.longitude = -180 +      expect(subject.valid?).to be_truthy +    end +    it "should reject < -180 value" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.longitude = -180.0001 +      expect(subject.valid?).to be_falsey +    end +    it "should accept 180 value" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.longitude = 180 +      expect(subject.valid?).to be_truthy +    end +    it "should reject > 180 value" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.longitude = 180.0001 +      expect(subject.valid?).to be_falsey +    end +  end + +  describe ".long_lat" do +    it "should accept longitude and latitude both as nil" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.longitude = nil +      subject.latitude = nil +      expect(subject.valid?).to be_truthy +    end +    it "should accept longitude and latitude both numerical" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.longitude = 10 +      subject.latitude = 10 +      expect(subject.valid?).to be_truthy +    end +    it "should reject longitude nil with latitude numerical" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.longitude = nil +      subject.latitude = 10 +      expect(subject.valid?).to be_falsey +    end +    it "should reject longitude numerical with latitude nil" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      subject.longitude = 10 +      subject.latitude = nil +      expect(subject.valid?).to be_falsey +    end +  end  +   + +  describe ".children_in_depth" do +    it "should return all the deepest children from stop area" do +      subject = create :stop_area, :area_type => "StopPlace" +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint", :parent => subject  +      commercial_stop_point2 = create :stop_area, :area_type => "CommercialStopPoint", :parent => commercial_stop_point +      quay = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      expect(subject.children_in_depth).to match_array([commercial_stop_point, commercial_stop_point2, quay]) +    end +    it "should return only the deepest children from stop area" do +      subject = create :stop_area, :area_type => "StopPlace" +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint", :parent => subject  +      commercial_stop_point2 = create :stop_area, :area_type => "CommercialStopPoint", :parent => commercial_stop_point +      quay = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      expect(subject.children_at_base).to match_array([quay]) +    end +  end + +  describe ".stop_area_type" do +    it "should have area_type of BoardingPosition when stop_area_type is set to boarding_position" do +      subject = create :stop_area, :stop_area_type => "boarding_position" +      expect(subject.area_type).to eq("BoardingPosition") +    end +    it "should have area_type of Quay when stop_area_type is set to quay" do +      subject = create :stop_area, :stop_area_type => "quay" +      expect(subject.area_type).to eq("Quay") +    end +    it "should have area_type of CommercialStopPoint when stop_area_type is set to commercial_stop_point" do +      subject = create :stop_area, :stop_area_type => "commercial_stop_point" +      expect(subject.area_type).to eq("CommercialStopPoint") +    end +    it "should have area_type of StopPlace when stop_area_type is set to stop_place" do +      subject = create :stop_area, :stop_area_type => "stop_place" +      expect(subject.area_type).to eq("StopPlace") +    end +    it "should have area_type of ITL when stop_area_type is set to itl" do +      subject = create :stop_area, :stop_area_type => "itl" +      expect(subject.area_type).to eq("ITL") +    end +  end + +  describe ".parent" do +    it "should check if parent method exists" do +      subject = create :stop_area, :parent_id => commercial_stop_point.id +      expect(subject.parent).to eq(commercial_stop_point) +    end +  end + +  describe ".possible_children" do     +     +    it "should find no possible descendant for stop area type quay" do +      subject = create :stop_area, :area_type => "Quay" +      expect(subject.possible_children).to eq([])  +    end + +    it "should find no possible descendant for stop area type boarding position" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      expect(subject.possible_children).to eq([])  +    end + +    it "should find descendant of type quay or boarding position for stop area type commercial stop point" do +      subject = create :stop_area, :area_type => "CommercialStopPoint" +      expect(subject.possible_children).to match_array([quay, boarding_position])  +    end + +    it "should find no children of type stop place or commercial stop point for stop area type stop place" do +      subject = create :stop_area, :area_type => "StopPlace" +      expect(subject.possible_children).to match_array([stop_place, commercial_stop_point])  +    end + +    it "should find no children of type ITL for stop area type ITL" do +      subject = create :stop_area, :area_type => "ITL" +      expect(subject.possible_children).to match_array([stop_place, commercial_stop_point, quay, boarding_position])  +    end + +  end + +  describe ".possible_parents" do + +    it "should find parent type commercial stop point for stop area type boarding position" do +      subject = create :stop_area, :area_type => "BoardingPosition" +      expect(subject.possible_parents).to eq([commercial_stop_point])  +    end + +    it "should find parent type commercial stop point for stop area type quay" do +      subject = create :stop_area, :area_type => "Quay" +      expect(subject.possible_parents).to eq([commercial_stop_point])  +    end     + +    it "should find parent type stop place for stop area type commercial stop point" do +      subject = create :stop_area, :area_type => "CommercialStopPoint" +      expect(subject.possible_parents).to eq([stop_place])  +    end     + +    it "should find parent type stop place for stop area type stop place" do +      subject = create :stop_area, :area_type => "StopPlace" +      expect(subject.possible_parents).to eq([stop_place])  +    end     + +  end + + +  describe ".near" do + +    let(:stop_area) { create :stop_area, :latitude => 1, :longitude => 1 } +    let(:stop_area2) { create :stop_area, :latitude => 1, :longitude => 1 } +     +    it "should find a StopArea at 300m from given origin" do +      expect(Chouette::StopArea.near(stop_area.to_lat_lng.endpoint(0, 0.250, :units => :kms))).to eq([stop_area]) +    end + +    it "should not find a StopArea at more than 300m from given origin" do +      expect(Chouette::StopArea.near(stop_area2.to_lat_lng.endpoint(0, 0.350, :units => :kms))).to be_empty +    end + +  end + +  describe "#to_lat_lng" do +     +    it "should return nil if latitude is nil" do +      subject.latitude = nil +      expect(subject.to_lat_lng).to be_nil +    end + +    it "should return nil if longitude is nil" do +      subject.longitude = nil +      expect(subject.to_lat_lng).to be_nil +    end + +  end + +  describe "#geometry" do +     +    it "should be nil when to_lat_lng is nil" do +      allow(subject).to receive_messages :to_lat_lng => nil +      expect(subject.geometry).to be_nil +    end + +  end + +  describe ".bounds" do +     +    it "should return transform coordinates in floats" do +      allow(Chouette::StopArea.connection).to receive_messages :select_rows => [["113.5292500000000000", "22.1127580000000000", "113.5819330000000000", "22.2157050000000000"]] +      expect(GeoRuby::SimpleFeatures::Envelope).to receive(:from_coordinates).with([[113.5292500000000000, 22.1127580000000000], [113.5819330000000000, 22.2157050000000000]]) +      Chouette::StopArea.bounds +    end + +  end + +  describe "#default_position" do +     +    it "should return nil when StopArea.bounds is nil" do +      allow(Chouette::StopArea).to receive_messages :bounds => nil +      expect(subject.default_position).to be_nil +    end + +    it "should return StopArea.bounds center" do +      allow(Chouette::StopArea).to receive_messages :bounds => double(:center => "center") +      expect(subject.default_position).to eq(Chouette::StopArea.bounds.center) +    end + +  end + +  describe "#children_at_base" do +    it "should have 2 children_at_base" do +      subject = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => subject +      quay1 = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      quay2 = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      expect(subject.children_at_base.size).to eq(2) +    end +   end  + + +  describe "#generic_access_link_matrix" do +    it "should have no access_links in matrix with no access_point" do +      subject = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => subject +      expect(subject.generic_access_link_matrix.size).to eq(0) +    end +    it "should have 4 generic_access_links in matrix with 2 access_points" do +      subject = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => subject +      access_point1 = create :access_point, :stop_area => subject +      access_point2 = create :access_point, :stop_area => subject +      expect(subject.generic_access_link_matrix.size).to eq(4) +    end +   end  +  describe "#detail_access_link_matrix" do +    it "should have no access_links in matrix with no access_point" do +      subject = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => subject +      quay1 = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      quay2 = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      expect(subject.detail_access_link_matrix.size).to eq(0) +    end +    it "should have 8 detail_access_links in matrix with 2 children_at_base and 2 access_points" do +      subject = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => subject +      quay1 = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      quay2 = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      access_point1 = create :access_point, :stop_area => subject +      access_point2 = create :access_point, :stop_area => subject +      expect(subject.detail_access_link_matrix.size).to eq(8) +    end +   end  +  describe "#parents" do +    it "should return parent hireachy list" do +      stop_place = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => stop_place +      subject = create :stop_area, :parent => commercial_stop_point, :area_type => "Quay" +      expect(subject.parents.size).to eq(2) +    end +    it "should return empty parent hireachy list" do +      subject = create :stop_area, :area_type => "Quay" +      expect(subject.parents.size).to eq(0) +    end +  end +   +  describe "#clean_invalid_access_links" do +    it "should remove invalid access links" do +      # subject is a CSP with a SP as parent, a quay as child +      # 2 access_points of SP have access_link, one on subject, one on subject child +      # when detaching subject from SP, both access_links must be deleted +      stop_place = create :stop_area, :area_type => "StopPlace"  +      subject = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => stop_place +      access_point1 = create :access_point, :stop_area => stop_place +      access_point2 = create :access_point, :stop_area => stop_place +      quay = create :stop_area, :parent => subject, :area_type => "Quay" +      access_link1 = create :access_link, :stop_area => subject, :access_point => access_point1 +      access_link2 = create :access_link, :stop_area => quay, :access_point => access_point2 +      subject.save  +      expect(subject.access_links.size).to eq(1) +      expect(quay.access_links.size).to eq(1) +      subject.parent=nil +      subject.save  +      subject.reload +      expect(subject.access_links.size).to eq(0) +      expect(quay.access_links.size).to eq(0) +    end +    it "should not remove still valid access links" do +      # subject is a Q of CSP with a SP as parent +      # 2 access_points, one of SP, one of CSP have access_link on subject +      # when changing subject CSP to another CSP of same SP +      # one access_links must be kept +      stop_place = create :stop_area, :area_type => "StopPlace"  +      commercial_stop_point1 = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => stop_place +      commercial_stop_point2 = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => stop_place +      access_point1 = create :access_point, :stop_area => stop_place +      access_point2 = create :access_point, :stop_area => commercial_stop_point1 +      subject = create :stop_area, :parent => commercial_stop_point1, :area_type => "Quay" +      access_link1 = create :access_link, :stop_area => subject, :access_point => access_point1 +      access_link2 = create :access_link, :stop_area => subject, :access_point => access_point2 +      subject.save  +      expect(subject.access_links.size).to eq(2) +      subject.parent=commercial_stop_point2 +      subject.save  +      subject.reload +      expect(subject.access_links.size).to eq(1) +    end +  end +   +  describe "#coordinates" do +    it "should convert coordinates into latitude/longitude" do +     subject = create :stop_area, :area_type => "BoardingPosition", :coordinates => "45.123,120.456" +     expect(subject.longitude).to be_within(0.001).of(120.456) +     expect(subject.latitude).to be_within(0.001).of(45.123) +   end +    it "should set empty coordinates into nil latitude/longitude" do +     subject = create :stop_area, :area_type => "BoardingPosition", :coordinates => "45.123,120.456" +     expect(subject.longitude).to be_within(0.001).of(120.456) +     expect(subject.latitude).to be_within(0.001).of(45.123) +     subject.coordinates = "" +     subject.save +     expect(subject.longitude).to be_nil +     expect(subject.latitude).to be_nil +   end +    it "should convert latitude/longitude into coordinates" do +     subject = create :stop_area, :area_type => "BoardingPosition", :longitude => 120.456, :latitude => 45.123 +     expect(subject.coordinates).to eq("45.123,120.456") +   end +    it "should convert nil latitude/longitude into empty coordinates" do +     subject = create :stop_area, :area_type => "BoardingPosition", :longitude => nil, :latitude => nil +     expect(subject.coordinates).to eq("") +   end +    it "should accept valid coordinates" do +     subject = create :stop_area, :area_type => "BoardingPosition", :coordinates => "45.123,120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "45.123, 120.456" +     expect(subject.valid?).to be_truthy +     expect(subject.longitude).to be_within(0.001).of(120.456) +     expect(subject.latitude).to be_within(0.001).of(45.123) +     subject.coordinates = "45.123,  -120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "45.123 ,120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "45.123   ,   120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = " 45.123,120.456" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "45.123,120.456  " +     expect(subject.valid?).to be_truthy +    end +    it "should accept valid coordinates on limits" do +     subject = create :stop_area, :area_type => "BoardingPosition", :coordinates => "90,180" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "-90,-180" +     expect(subject.valid?).to be_truthy +     subject.coordinates = "-90.,180." +     expect(subject.valid?).to be_truthy +     subject.coordinates = "-90.0,180.00" +     expect(subject.valid?).to be_truthy +    end +    it "should reject invalid coordinates" do +     subject = create :stop_area, :area_type => "BoardingPosition" +     subject.coordinates = ",12" +     expect(subject.valid?).to be_falsey +     subject.coordinates = "-90" +     expect(subject.valid?).to be_falsey +     subject.coordinates = "-90.1,180." +     expect(subject.valid?).to be_falsey +     subject.coordinates = "-90.0,180.1" +     expect(subject.valid?).to be_falsey +     subject.coordinates = "-91.0,18.1" +     expect(subject.valid?).to be_falsey +    end +  end + +  describe "#duplicate" do +      it "should be a copy of" do +        stop_place = create :stop_area, :area_type => "StopPlace"  +        subject = create :stop_area, :area_type => "CommercialStopPoint" ,:parent => stop_place, :coordinates => "45.123,120.456" +        access_point1 = create :access_point, :stop_area => subject +        access_point2 = create :access_point, :stop_area => subject +        quay1 = create :stop_area, :parent => subject, :area_type => "Quay" +        target=subject.duplicate +        expect(target.id).to be_nil +        expect(target.name).to eq(I18n.t("activerecord.copy", name: subject.name)) +        expect(target.objectid).to eq(subject.objectid+"_1") +        expect(target.area_type).to eq(subject.area_type) +        expect(target.parent).to be_nil +        expect(target.children.size).to eq(0) +        expect(target.access_points.size).to eq(0) +        expect(target.coordinates).to eq("45.123,120.456") +      end +  end + + +end diff --git a/spec/models/chouette/stop_point_spec.rb b/spec/models/chouette/stop_point_spec.rb new file mode 100644 index 000000000..212c32e1a --- /dev/null +++ b/spec/models/chouette/stop_point_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Chouette::StopPoint, :type => :model do +  let!(:vehicle_journey) { create(:vehicle_journey)} +  subject { Chouette::Route.find( vehicle_journey.route_id).stop_points.first } + +  it { is_expected.to validate_uniqueness_of :objectid } +  it { is_expected.to validate_presence_of :stop_area } + +  describe '#objectid' do +    subject { super().objectid } +    it { is_expected.to be_kind_of(Chouette::ObjectId) } +  end + +  describe "#destroy" do +    before(:each) do +      @vehicle = create(:vehicle_journey) +      @stop_point = Chouette::Route.find( @vehicle.route_id).stop_points.first +    end +    def vjas_stop_point_ids( vehicle_id) +      Chouette::VehicleJourney.find( vehicle_id).vehicle_journey_at_stops.map(&:stop_point_id) +    end +    def jpsp_stop_point_ids( journey_id) +      Chouette::JourneyPattern.find( journey_id).stop_points.map(&:id) +    end +    it "should remove dependent vehicle_journey_at_stop" do +      expect(vjas_stop_point_ids(@vehicle.id)).to include(@stop_point.id) + +      @stop_point.destroy + +      expect(vjas_stop_point_ids(@vehicle.id)).not_to include(@stop_point.id) +    end +    it "should remove dependent journey_pattern_stop_point" do +      expect(jpsp_stop_point_ids(@vehicle.journey_pattern_id)).to include(@stop_point.id) + +      @stop_point.destroy + +      expect(jpsp_stop_point_ids(@vehicle.journey_pattern_id)).not_to include(@stop_point.id) +    end +  end +end diff --git a/spec/models/chouette/time_table_period_spec.rb b/spec/models/chouette/time_table_period_spec.rb new file mode 100644 index 000000000..07dc602cb --- /dev/null +++ b/spec/models/chouette/time_table_period_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Chouette::TimeTablePeriod, :type => :model do + +  let!(:time_table) { create(:time_table)} +  subject { create(:time_table_period ,:time_table => time_table, :period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,6) ) } +  let!(:p2) {create(:time_table_period ,:time_table => time_table, :period_start => Date.new(2014,7,6), :period_end => Date.new(2014,7,14) ) }  + +  it { is_expected.to validate_presence_of :period_start } +  it { is_expected.to validate_presence_of :period_end } +   +  describe "#overlap" do +    context "when periods intersect, " do +      it "should detect period overlap" do +         expect(subject.overlap?(p2)).to be_truthy +         expect(p2.overlap?(subject)).to be_truthy +      end +    end +    context "when periods don't intersect, " do +      before(:each) do +        p2.period_start = Date.new(2014,7,7) +      end +      it "should not detect period overlap" do +         expect(subject.overlap?(p2)).to be_falsey +         expect(p2.overlap?(subject)).to be_falsey +      end +    end +    context "when period 1 contains period 2, " do +      before(:each) do +        p2.period_start = Date.new(2014,7,1) +        p2.period_end = Date.new(2014,7,6) +      end +      it "should detect period overlap" do +         expect(subject.overlap?(p2)).to be_truthy +         expect(p2.overlap?(subject)).to be_truthy +      end +    end +  end +  describe "#contains" do +    context "when periods intersect, " do +      it "should not detect period inclusion" do +         expect(subject.contains?(p2)).to be_falsey +         expect(p2.contains?(subject)).to be_falsey +      end +    end +    context "when periods don't intersect, " do +      before(:each) do +        p2.period_start = Date.new(2014,7,7) +      end +      it "should not detect period inclusion" do +         expect(subject.contains?(p2)).to be_falsey +         expect(p2.contains?(subject)).to be_falsey +      end +    end +    context "when period 1 contains period 2, " do +      before(:each) do +        p2.period_start = Date.new(2014,7,1) +        p2.period_end = Date.new(2014,7,6) +      end +      it "should detect period inclusion" do +         expect(subject.contains?(p2)).to be_truthy +         expect(p2.contains?(subject)).to be_falsey +      end +    end +  end +end diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb new file mode 100644 index 000000000..a3a361e7b --- /dev/null +++ b/spec/models/chouette/time_table_spec.rb @@ -0,0 +1,1266 @@ +require 'spec_helper' + +describe Chouette::TimeTable, :type => :model do + +  subject { create(:time_table) } + +  it { is_expected.to validate_presence_of :comment } +  it { is_expected.to validate_uniqueness_of :objectid } + +  describe "#periods_max_date" do +    context "when all period extends from 04/10/2013 to 04/15/2013," do +      before(:each) do +        p1 = Chouette::TimeTablePeriod.new( :period_start => Date.strptime("04/10/2013", '%m/%d/%Y'), :period_end => Date.strptime("04/12/2013", '%m/%d/%Y')) +        p2 = Chouette::TimeTablePeriod.new( :period_start => Date.strptime("04/13/2013", '%m/%d/%Y'), :period_end => Date.strptime("04/15/2013", '%m/%d/%Y')) +        subject.periods = [ p1, p2] +        subject.save +      end + +      it "should retreive 04/15/2013" do +        expect(subject.periods_max_date).to eq(Date.strptime("04/15/2013", '%m/%d/%Y')) +      end +      context "when 04/15/2013 is excluded, periods_max_dates selects the day before" do +        before(:each) do +          excluded_date = Date.strptime("04/15/2013", '%m/%d/%Y') +          subject.dates = [ Chouette::TimeTableDate.new( :date => excluded_date, :in_out => false)] +          subject.save +        end +        it "should retreive 04/14/2013" do +          expect(subject.periods_max_date).to eq(Date.strptime("04/14/2013", '%m/%d/%Y')) +        end +      end +      context "when day_types select only sunday and saturday," do +        before(:each) do +          # jeudi, vendredi +          subject.update_attributes( :int_day_types => (2**(1+6) + 2**(1+7))) +        end +        it "should retreive 04/14/2013" do +          expect(subject.periods_max_date).to eq(Date.strptime("04/14/2013", '%m/%d/%Y')) +        end +      end +      context "when day_types select only friday," do +        before(:each) do +          # jeudi, vendredi +          subject.update_attributes( :int_day_types => (2**(1+6))) +        end +        it "should retreive 04/12/2013" do +          expect(subject.periods_max_date).to eq(Date.strptime("04/13/2013", '%m/%d/%Y')) +        end +      end +      context "when day_types select only thursday," do +        before(:each) do +          # mardi +          subject.update_attributes( :int_day_types => (2**(1+2))) +        end +        it "should retreive 04/12/2013" do +          # 04/15/2013 is monday ! +          expect(subject.periods_max_date).to be_nil +        end +      end +    end +  end + +describe "update_attributes on periods and dates" do + +    context "update days selection" do +        it "should update start_date and end_end" do +            days_hash = {}.tap do |hash| +                [ :monday,:tuesday,:wednesday,:thursday,:friday,:saturday,:sunday ].each { |d| hash[d] = false } +            end +            subject.update_attributes( days_hash) + +            read = Chouette::TimeTable.find( subject.id ) +            expect(read.start_date).to eq(read.dates.select{|d| d.in_out}.map(&:date).compact.min) +            expect(read.end_date).to eq(read.dates.select{|d| d.in_out}.map(&:date).compact.max) + +        end +    end +    context "add a new period" do +        let!( :new_start_date ){ subject.start_date - 20.days } +        let!( :new_end_date ){ subject.end_date + 20.days } +        let!( :new_period_attributes ) { +            pa = periods_attributes +            pa[ "11111111111" ] = { "period_end" => new_end_date, "period_start" => new_start_date, "_destroy" => "", "position" => pa.size.to_s, "id" => "", "time_table_id" => subject.id.to_s} +            pa +        } +        it "should update start_date and end_end" do +            subject.update_attributes( :periods_attributes => new_period_attributes) + +            read = Chouette::TimeTable.find( subject.id ) +            expect(read.start_date).to eq(new_start_date) +            expect(read.end_date).to eq(new_end_date) +        end +    end +    context "update period end" do +        let!( :new_end_date ){ subject.end_date + 20.days } +        let!( :new_period_attributes ) { +            pa = periods_attributes +            pa[ "0" ].merge! "period_end" => new_end_date +            pa +        } +        it "should update end_date" do +            subject.update_attributes :periods_attributes => new_period_attributes + +            read = Chouette::TimeTable.find( subject.id ) +            expect(read.end_date).to eq(new_end_date) +        end +    end +    context "update period start" do +        let!( :new_start_date ){ subject.start_date - 20.days } +        let!( :new_period_attributes ) { +            pa = periods_attributes +            pa[ "0" ].merge! "period_start" => new_start_date +            pa +        } +        it "should update start_date" do +            subject.update_attributes :periods_attributes => new_period_attributes + +            read = Chouette::TimeTable.find( subject.id ) +            expect(read.start_date).to eq(new_start_date) +        end +    end +    context "remove periods and dates and add a new period" do +        let!( :new_start_date ){ subject.start_date + 1.days } +        let!( :new_end_date ){ subject.end_date - 1.days } +        let!( :new_dates_attributes ) { +            da = dates_attributes +            da.each { |k,v| v.merge! "_destroy" => true} +            da +        } +        let!( :new_period_attributes ) { +            pa = periods_attributes +            pa.each { |k,v| v.merge! "_destroy" => true} +            pa[ "11111111111" ] = { "period_end" => new_end_date, "period_start" => new_start_date, "_destroy" => "", "position" => pa.size.to_s, "id" => "", "time_table_id" => subject.id.to_s} +            pa +        } +        it "should update start_date and end_date with new period added" do +            subject.update_attributes :periods_attributes => new_period_attributes, :dates_attributes => new_dates_attributes + +            read = Chouette::TimeTable.find( subject.id ) +            expect(read.start_date).to eq(new_start_date) +            expect(read.end_date).to eq(new_end_date) +        end +    end +    def dates_attributes +        {}.tap do |hash| +            subject.dates.each_with_index do |p, index| +                hash.merge! index.to_s => p.attributes.merge( "_destroy" => "" ) +            end +        end +    end +    def periods_attributes +        {}.tap do |hash| +            subject.periods.each_with_index do |p, index| +                hash.merge! index.to_s => p.attributes.merge( "_destroy" => "" ) +            end +        end +    end +end + +  describe "#periods_min_date" do +    context "when all period extends from 04/10/2013 to 04/15/2013," do +      before(:each) do +        p1 = Chouette::TimeTablePeriod.new( :period_start => Date.strptime("04/10/2013", '%m/%d/%Y'), :period_end => Date.strptime("04/12/2013", '%m/%d/%Y')) +        p2 = Chouette::TimeTablePeriod.new( :period_start => Date.strptime("04/13/2013", '%m/%d/%Y'), :period_end => Date.strptime("04/15/2013", '%m/%d/%Y')) +        subject.periods = [ p1, p2] +        subject.save +      end + +      it "should retreive 04/10/2013" do +        expect(subject.periods_min_date).to eq(Date.strptime("04/10/2013", '%m/%d/%Y')) +      end +      context "when 04/10/2013 is excluded, periods_min_dates select the day after" do +        before(:each) do +          excluded_date = Date.strptime("04/10/2013", '%m/%d/%Y') +          subject.dates = [ Chouette::TimeTableDate.new( :date => excluded_date, :in_out => false)] +          subject.save +        end +        it "should retreive 04/11/2013" do +          expect(subject.periods_min_date).to eq(Date.strptime("04/11/2013", '%m/%d/%Y')) +        end +      end +      context "when day_types select only tuesday and friday," do +        before(:each) do +          # jeudi, vendredi +          subject.update_attributes( :int_day_types => (2**(1+4) + 2**(1+5))) +        end +        it "should retreive 04/11/2013" do +          expect(subject.periods_min_date).to eq(Date.strptime("04/11/2013", '%m/%d/%Y')) +        end +      end +      context "when day_types select only friday," do +        before(:each) do +          # jeudi, vendredi +          subject.update_attributes( :int_day_types => (2**(1+5))) +        end +        it "should retreive 04/12/2013" do +          expect(subject.periods_min_date).to eq(Date.strptime("04/12/2013", '%m/%d/%Y')) +        end +      end +      context "when day_types select only thursday," do +        before(:each) do +          # mardi +          subject.update_attributes( :int_day_types => (2**(1+2))) +        end +        it "should retreive 04/12/2013" do +          # 04/15/2013 is monday ! +          expect(subject.periods_min_date).to be_nil +        end +      end +    end +  end +  describe "#periods.build" do +    it "should add a new instance of period, and periods_max_date should not raise error" do +      period = subject.periods.build +      subject.periods_max_date +      expect(period.period_start).to be_nil +      expect(period.period_end).to be_nil +    end +  end +  describe "#periods" do +    context "when a period is added," do +      before(:each) do +        subject.periods << Chouette::TimeTablePeriod.new( :period_start => (subject.bounding_dates.min - 1), :period_end => (subject.bounding_dates.max + 1)) +        subject.save +      end +      it "should update shortcut" do +        expect(subject.start_date).to eq(subject.bounding_dates.min) +        expect(subject.end_date).to eq(subject.bounding_dates.max) +      end +    end +    context "when a period is removed," do +      before(:each) do +        subject.dates = [] +        subject.periods = [] +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => 4.days.since.to_date, +                              :period_end => 6.days.since.to_date) +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => 1.days.since.to_date, +                              :period_end => 10.days.since.to_date) +        subject.save +        subject.periods = subject.periods - [subject.periods.last] +        subject.save_shortcuts +      end +      def read_tm +        Chouette::TimeTable.find subject.id +      end +      it "should update shortcut" do +        tm = read_tm +        expect(subject.start_date).to eq(subject.bounding_dates.min) +        expect(subject.start_date).to eq(tm.bounding_dates.min) +        expect(subject.start_date).to eq(4.days.since.to_date) +        expect(subject.end_date).to eq(subject.bounding_dates.max) +        expect(subject.end_date).to eq(tm.bounding_dates.max) +        expect(subject.end_date).to eq(6.days.since.to_date) +      end +    end +    context "when a period is updated," do +      before(:each) do +        subject.dates = [] +        subject.periods = [] +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => 4.days.since.to_date, +                              :period_end => 6.days.since.to_date) +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => 1.days.since.to_date, +                              :period_end => 10.days.since.to_date) +        subject.save +        subject.periods.last.period_end = 15.days.since.to_date +        subject.save +      end +      def read_tm +        Chouette::TimeTable.find subject.id +      end +      it "should update shortcut" do +        tm = read_tm +        expect(subject.start_date).to eq(subject.bounding_dates.min) +        expect(subject.start_date).to eq(tm.bounding_dates.min) +        expect(subject.start_date).to eq(1.days.since.to_date) +        expect(subject.end_date).to eq(subject.bounding_dates.max) +        expect(subject.end_date).to eq(tm.bounding_dates.max) +        expect(subject.end_date).to eq(15.days.since.to_date) +      end +    end + +  end +  describe "#periods.valid?" do +    context "when an empty period is set," do +      it "should not save tm if period invalid" do +        subject = Chouette::TimeTable.new({"comment"=>"test", +                                           "version"=>"", +                                           "monday"=>"0", +                                           "tuesday"=>"0", +                                           "wednesday"=>"0", +                                           "thursday"=>"0", +                                           "friday"=>"0", +                                           "saturday"=>"0", +                                           "sunday"=>"0", +                                           "objectid"=>"", +                                           "periods_attributes"=>{"1397136188334"=>{"period_start"=>"", +                                           "period_end"=>"", +                                           "_destroy"=>""}}}) +        subject.save +        expect(subject.id).to be_nil +      end +    end +    context "when a valid period is set," do +      it "it should save tm if period valid" do +        subject = Chouette::TimeTable.new({"comment"=>"test", +                                           "version"=>"", +                                           "monday"=>"1", +                                           "tuesday"=>"1", +                                           "wednesday"=>"1", +                                           "thursday"=>"1", +                                           "friday"=>"1", +                                           "saturday"=>"1", +                                           "sunday"=>"1", +                                           "objectid"=>"", +                                           "periods_attributes"=>{"1397136188334"=>{"period_start"=>"2014-01-01", +                                           "period_end"=>"2015-01-01", +                                           "_destroy"=>""}}}) +        subject.save +        tm = Chouette::TimeTable.find subject.id +        expect(tm.periods.empty?).to be_falsey +        expect(tm.start_date).to eq(Date.new(2014, 01, 01)) +        expect(tm.end_date).to eq(Date.new(2015, 01, 01)) + +      end +    end +  end + +  describe "#dates" do +    context "when a date is added," do +      before(:each) do +        subject.dates << Chouette::TimeTableDate.new( :date => (subject.bounding_dates.max + 1), :in_out => true) +        subject.save +      end +      it "should update shortcut" do +        expect(subject.start_date).to eq(subject.bounding_dates.min) +        expect(subject.end_date).to eq(subject.bounding_dates.max) +      end +    end +    context "when a date is removed," do +      before(:each) do +        subject.periods = [] +        subject.dates = subject.dates - [subject.bounding_dates.max + 1] +        subject.save_shortcuts +      end +      it "should update shortcut" do +        expect(subject.start_date).to eq(subject.bounding_dates.min) +        expect(subject.end_date).to eq(subject.bounding_dates.max) +      end +    end +    context "when all the dates and periods are removed," do +      before(:each) do +        subject.periods = [] +        subject.dates = [] +        subject.save_shortcuts +      end +      it "should update shortcut" do +        expect(subject.start_date).to be_nil +        expect(subject.end_date).to be_nil +      end +    end +    context "when a date is updated," do +      before(:each) do +        subject.dates = [] + +        subject.periods = [] +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => 4.days.since.to_date, +                              :period_end => 6.days.since.to_date) +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => 1.days.since.to_date, +                              :period_end => 10.days.since.to_date) +        subject.dates << Chouette::TimeTableDate.new( :date => 10.days.since.to_date, :in_out => true) +        subject.save +        subject.dates.last.date = 15.days.since.to_date +        subject.save +      end +      def read_tm +        Chouette::TimeTable.find subject.id +      end +      it "should update shortcut" do +        tm = read_tm +        expect(subject.start_date).to eq(subject.bounding_dates.min) +        expect(subject.start_date).to eq(tm.bounding_dates.min) +        expect(subject.start_date).to eq(1.days.since.to_date) +        expect(subject.end_date).to eq(subject.bounding_dates.max) +        expect(subject.end_date).to eq(tm.bounding_dates.max) +        expect(subject.end_date).to eq(15.days.since.to_date) +      end +    end +  end +  describe "#dates.valid?" do +    it "should not save tm if date invalid" do +      subject = Chouette::TimeTable.new({"comment"=>"test", +                                         "version"=>"", +                                         "monday"=>"0", +                                         "tuesday"=>"0", +                                         "wednesday"=>"0", +                                         "thursday"=>"0", +                                         "friday"=>"0", +                                         "saturday"=>"0", +                                         "sunday"=>"0", +                                         "objectid"=>"", +                                         "dates_attributes"=>{"1397136189216"=>{"date"=>"", +                                         "_destroy"=>"", "in_out" => true}}}) +      subject.save +      expect(subject.id).to be_nil +    end +    it "it should save tm if date valid" do +      subject = Chouette::TimeTable.new({"comment"=>"test", +                                         "version"=>"", +                                         "monday"=>"1", +                                         "tuesday"=>"1", +                                         "wednesday"=>"1", +                                         "thursday"=>"1", +                                         "friday"=>"1", +                                         "saturday"=>"1", +                                         "sunday"=>"1", +                                         "objectid"=>"", +                                         "dates_attributes"=>{"1397136189216"=>{"date"=>"2015-01-01", +                                         "_destroy"=>"", "in_out" => true}}}) +      subject.save +      tm = Chouette::TimeTable.find subject.id +      expect(tm.dates.empty?).to be_falsey +      expect(tm.start_date).to eq(Date.new(2015, 01, 01)) +      expect(tm.end_date).to eq(Date.new(2015, 01, 01)) +    end +  end + +  describe "#valid_days" do +    it "should begin with position 0" do +      subject.int_day_types = 128 +      expect(subject.valid_days).to eq([6]) +    end +  end + +  describe "#intersects" do +    it "should return day if a date equal day" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1") +      time_table.dates << Chouette::TimeTableDate.new( :date => Date.today, :in_out => true) +      expect(time_table.intersects([Date.today])).to eq([Date.today]) +    end + +    it "should return [] if a period not include days" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 12) +      time_table.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2013, 05, 27), +                              :period_end => Date.new(2013, 05, 30)) +      expect(time_table.intersects([ Date.new(2013, 05, 29),  Date.new(2013, 05, 30)])).to eq([]) +    end + +    it "should return days if a period include day" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 12) # Day type monday and tuesday +      time_table.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2013, 05, 27), +                              :period_end => Date.new(2013, 05, 30)) +      expect(time_table.intersects([ Date.new(2013, 05, 27),  Date.new(2013, 05, 28)])).to eq([ Date.new(2013, 05, 27),  Date.new(2013, 05, 28)]) +    end + + +  end + +  describe "#include_day?" do +    it "should return true if a date equal day" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1") +      time_table.dates << Chouette::TimeTableDate.new( :date => Date.today, :in_out => true) +      expect(time_table.include_day?(Date.today)).to eq(true) +    end + +    it "should return true if a period include day" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 12) # Day type monday and tuesday +      time_table.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2013, 05, 27), +                              :period_end => Date.new(2013, 05, 29)) +      expect(time_table.include_day?( Date.new(2013, 05, 27))).to eq(true) +    end +  end + +  describe "#include_in_dates?" do +    it "should return true if a date equal day" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1") +      time_table.dates << Chouette::TimeTableDate.new( :date => Date.today, :in_out => true) +      expect(time_table.include_in_dates?(Date.today)).to eq(true) +    end + +    it "should return false if a period include day  but that is exclued" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 12) # Day type monday and tuesday +      excluded_date = Date.new(2013, 05, 27) +      time_table.dates << Chouette::TimeTableDate.new( :date => excluded_date, :in_out => false) +      expect(time_table.include_in_dates?( excluded_date)).to be_falsey +    end +  end + +  describe "#include_in_periods?" do +    it "should return true if a period include day" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 4) +      time_table.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2012, 1, 1), +                              :period_end => Date.new(2012, 01, 30)) +      expect(time_table.include_in_periods?(Date.new(2012, 1, 2))).to eq(true) +    end + +    it "should return false if a period include day  but that is exclued" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 12) # Day type monday and tuesday +      excluded_date = Date.new(2013, 05, 27) +      time_table.dates << Chouette::TimeTableDate.new( :date => excluded_date, :in_out => false) +      time_table.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2013, 05, 27), +                              :period_end => Date.new(2013, 05, 29)) +      expect(time_table.include_in_periods?( excluded_date)).to be_falsey +    end +  end + +  describe "#include_in_overlap_dates?" do +    it "should return true if a day is included in overlap dates" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 4) +      time_table.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2012, 1, 1), +                              :period_end => Date.new(2012, 01, 30)) +      time_table.dates << Chouette::TimeTableDate.new( :date => Date.new(2012, 1, 2), :in_out => true) +      expect(time_table.include_in_overlap_dates?(Date.new(2012, 1, 2))).to eq(true) +    end +    it "should return false if the day is excluded" do +      time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 4) +      time_table.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2012, 1, 1), +                              :period_end => Date.new(2012, 01, 30)) +      time_table.dates << Chouette::TimeTableDate.new( :date => Date.new(2012, 1, 2), :in_out => false) +      expect(time_table.include_in_overlap_dates?(Date.new(2012, 1, 2))).to be_falsey +    end +  end + +  describe "#dates" do +    it "should have with position 0" do +      expect(subject.dates.first.position).to eq(0) +    end +    context "when first date has been removed" do +      before do +        subject.dates.first.destroy +      end +      it "should begin with position 0" do +        expect(subject.dates.first.position).to eq(0) +      end +    end +  end +  describe "#validity_out_between?" do +    let(:empty_tm) {build(:time_table)} +    it "should be false if empty calendar" do +      expect(empty_tm.validity_out_between?( Date.today, Date.today + 7.day)).to be_falsey +    end +    it "should be true if caldendar is out during start_date and end_date period" do +      start_date = subject.bounding_dates.max - 2.day +      end_date = subject.bounding_dates.max + 2.day +      expect(subject.validity_out_between?( start_date, end_date)).to be_truthy +    end +    it "should be false if calendar is out on start date" do +      start_date = subject.bounding_dates.max +      end_date = subject.bounding_dates.max + 2.day +      expect(subject.validity_out_between?( start_date, end_date)).to be_falsey +    end +    it "should be false if calendar is out on end date" do +      start_date = subject.bounding_dates.max - 2.day +      end_date = subject.bounding_dates.max +      expect(subject.validity_out_between?( start_date, end_date)).to be_truthy +    end +    it "should be false if calendar is out after start_date" do +      start_date = subject.bounding_dates.max + 2.day +      end_date = subject.bounding_dates.max + 4.day +      expect(subject.validity_out_between?( start_date, end_date)).to be_falsey +    end +  end +  describe "#validity_out_from_on?" do +    let(:empty_tm) {build(:time_table)} +    it "should be false if empty calendar" do +      expect(empty_tm.validity_out_from_on?( Date.today)).to be_falsey +    end +    it "should be true if caldendar ends on expected date" do +      expected_date = subject.bounding_dates.max +      expect(subject.validity_out_from_on?( expected_date)).to be_truthy +    end +    it "should be true if calendar ends before expected date" do +      expected_date = subject.bounding_dates.max + 30.day +      expect(subject.validity_out_from_on?( expected_date)).to be_truthy +    end +    it "should be false if calendars ends after expected date" do +      expected_date = subject.bounding_dates.max - 30.day +      expect(subject.validity_out_from_on?( expected_date)).to be_falsey +    end +  end +  describe "#bounding_dates" do +    context "when timetable contains only periods" do +      before do +        subject.dates = [] +        subject.save +      end +      it "should retreive periods.period_start.min and periods.period_end.max" do +        expect(subject.bounding_dates.min).to eq(subject.periods.map(&:period_start).min) +        expect(subject.bounding_dates.max).to eq(subject.periods.map(&:period_end).max) +      end +    end +    context "when timetable contains only dates" do +      before do +        subject.periods = [] +        subject.save +      end +      it "should retreive dates.min and dates.max" do +        expect(subject.bounding_dates.min).to eq(subject.dates.map(&:date).min) +        expect(subject.bounding_dates.max).to eq(subject.dates.map(&:date).max) +      end +    end +    it "should contains min date" do +      min_date = subject.bounding_dates.min +      subject.dates.each do |tm_date| +        expect(min_date <= tm_date.date).to be_truthy +      end +      subject.periods.each do |tm_period| +        expect(min_date <= tm_period.period_start).to be_truthy +      end + +    end +    it "should contains max date" do +      max_date = subject.bounding_dates.max +      subject.dates.each do |tm_date| +        expect(tm_date.date <= max_date).to be_truthy +      end +      subject.periods.each do |tm_period| +        expect(tm_period.period_end <= max_date).to be_truthy +      end + +    end +  end +  describe "#periods" do +    it "should begin with position 0" do +      expect(subject.periods.first.position).to eq(0) +    end +    context "when first period has been removed" do +      before do +        subject.periods.first.destroy +      end +      it "should begin with position 0" do +        expect(subject.periods.first.position).to eq(0) +      end +    end +    it "should have period_start before period_end" do +      period = Chouette::TimeTablePeriod.new +      period.period_start = Date.today +      period.period_end = Date.today + 10 +      expect(period.valid?).to be_truthy +    end +    it "should not have period_start after period_end" do +      period = Chouette::TimeTablePeriod.new +      period.period_start = Date.today +      period.period_end = Date.today - 10 +      expect(period.valid?).to be_falsey +    end +    it "should not have period_start equal to period_end" do +      period = Chouette::TimeTablePeriod.new +      period.period_start = Date.today +      period.period_end = Date.today +      expect(period.valid?).to be_falsey +    end +  end + +  describe "#effective_days_of_periods" do +      before do +        subject.periods.clear +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2014, 6, 30), +                              :period_end => Date.new(2014, 7, 6)) +        subject.int_day_types = 4|8|16 +      end +      it "should return monday to wednesday" do +        expect(subject.effective_days_of_periods.size).to eq(3) +        expect(subject.effective_days_of_periods[0]).to eq(Date.new(2014, 6, 30)) +        expect(subject.effective_days_of_periods[1]).to eq(Date.new(2014, 7, 1)) +        expect(subject.effective_days_of_periods[2]).to eq(Date.new(2014, 7, 2)) +      end +      it "should return thursday" do +        expect(subject.effective_days_of_periods(Chouette::TimeTable.valid_days(32)).size).to eq(1) +        expect(subject.effective_days_of_periods(Chouette::TimeTable.valid_days(32))[0]).to eq(Date.new(2014, 7, 3)) +      end + +  end + +  describe "#included_days" do +      before do +        subject.dates.clear +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,17), :in_out => false) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,19), :in_out => false) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true) +      end +      it "should return 3 dates" do +        days = subject.included_days +        expect(days.size).to eq(3) +        expect(days[0]).to eq(Date.new(2014, 7, 16)) +        expect(days[1]).to eq(Date.new(2014,7, 18)) +        expect(days[2]).to eq(Date.new(2014, 7,20)) +      end +  end + + + +  describe "#excluded_days" do +      before do +        subject.dates.clear +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,17), :in_out => false) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,19), :in_out => false) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true) +      end +      it "should return 3 dates" do +        days = subject.excluded_days +        expect(days.size).to eq(2) +        expect(days[0]).to eq(Date.new(2014, 7, 17)) +        expect(days[1]).to eq(Date.new(2014,7, 19)) +      end +  end + + + +  describe "#effective_days" do +      before do +        subject.periods.clear +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2014, 6, 30), +                              :period_end => Date.new(2014, 7, 6)) +        subject.int_day_types = 4|8|16 +        subject.dates.clear +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,1), :in_out => false) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true) +      end +      it "should return 5 dates" do +        days = subject.effective_days +        expect(days.size).to eq(5) +        expect(days[0]).to eq(Date.new(2014, 6, 30)) +        expect(days[1]).to eq(Date.new(2014, 7, 2)) +        expect(days[2]).to eq(Date.new(2014, 7, 16)) +        expect(days[3]).to eq(Date.new(2014, 7, 18)) +        expect(days[4]).to eq(Date.new(2014, 7, 20)) +      end +  end + + + +  describe "#optimize_periods" do +      before do +        subject.periods.clear +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2014, 6, 30), +                              :period_end => Date.new(2014, 7, 6)) +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2014, 7, 6), +                              :period_end => Date.new(2014, 7, 14)) +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2014, 6, 1), +                              :period_end => Date.new(2014, 6, 14)) +        subject.periods << Chouette::TimeTablePeriod.new( +                              :period_start => Date.new(2014, 6, 3), +                              :period_end => Date.new(2014, 6, 4)) +        subject.int_day_types = 4|8|16 +      end +      it "should return 2 ordered periods" do +        periods = subject.optimize_periods +        expect(periods.size).to eq(2) +        expect(periods[0].period_start).to eq(Date.new(2014, 6, 1)) +        expect(periods[0].period_end).to eq(Date.new(2014, 6, 14)) +        expect(periods[1].period_start).to eq(Date.new(2014, 6, 30)) +        expect(periods[1].period_end).to eq(Date.new(2014, 7, 14)) +      end +  end + +  describe "#add_included_day" do +      before do +        subject.dates.clear +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => false) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true) +      end +      it "should do nothing" do +        subject.add_included_day(Date.new(2014,7,16)) +        days = subject.included_days +        expect(days.size).to eq(2) +        expect(days.include?(Date.new(2014,7,16))).to be_truthy +        expect(days.include?(Date.new(2014,7,18))).to be_falsey +        expect(days.include?(Date.new(2014,7,20))).to be_truthy +      end +      it "should switch in_out flag" do +        subject.add_included_day(Date.new(2014,7,18)) +        days = subject.included_days +        expect(days.size).to eq(3) +        expect(days.include?(Date.new(2014,7,16))).to be_truthy +        expect(days.include?(Date.new(2014,7,18))).to be_truthy +        expect(days.include?(Date.new(2014,7,20))).to be_truthy +      end +      it "should add date" do +        subject.add_included_day(Date.new(2014,7,21)) +        days = subject.included_days +        expect(days.size).to eq(3) +        expect(days.include?(Date.new(2014,7,16))).to be_truthy +        expect(days.include?(Date.new(2014,7,20))).to be_truthy +        expect(days.include?(Date.new(2014,7,21))).to be_truthy +      end +  end + + +  describe "#merge!" do +    context "timetables have periods with common day_types " do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,5)) +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,6)) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.int_day_types = 4|16|32|128 +        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,5), :period_end => Date.new(2014,8,12)) +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,15), :period_end => Date.new(2014,7,25)) +        subject.merge! another_tt +        subject.reload +      end +      it "should have merged periods" do +        expect(subject.periods.size).to eq(3) +        expect(subject.periods[0].period_start).to eq(Date.new(2014, 6, 30)) +        expect(subject.periods[0].period_end).to eq(Date.new(2014, 7, 6)) +        expect(subject.periods[1].period_start).to eq(Date.new(2014, 7, 15)) +        expect(subject.periods[1].period_end).to eq(Date.new(2014, 7, 25)) +        expect(subject.periods[2].period_start).to eq(Date.new(2014, 8, 1)) +        expect(subject.periods[2].period_end).to eq(Date.new(2014, 8, 12)) +      end +      it "should have common day_types" do +        expect(subject.int_day_types).to eq(4|16|128) +      end +      it "should have dates for thursdays and fridays" do +        expect(subject.dates.size).to eq(4) +        expect(subject.dates[0].date).to eq(Date.new(2014,7,3)) +        expect(subject.dates[1].date).to eq(Date.new(2014,7,18)) +        expect(subject.dates[2].date).to eq(Date.new(2014,7,25)) +        expect(subject.dates[3].date).to eq(Date.new(2014,8,8)) +      end +    end + +  end + +  describe "#intersect!" do +    context "timetables have periods with common day_types " do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,6)) +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,20)) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.int_day_types = 4|16|32|128 +        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,6), :period_end => Date.new(2014,8,12)) +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,15), :period_end => Date.new(2014,7,25)) +        subject.intersect! another_tt +        subject.reload +      end +      it "should have no period" do +        expect(subject.periods.size).to eq(0) +       end +      it "should have no day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have date all common days" do +        expect(subject.dates.size).to eq(3) +        expect(subject.dates[0].date).to eq(Date.new(2014,7,16)) +        expect(subject.dates[1].date).to eq(Date.new(2014,7,19)) +        expect(subject.dates[2].date).to eq(Date.new(2014,8,6)) +      end +    end +    context "timetables have periods or dates " do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,17), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,19), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true) +        subject.int_day_types = 0 +        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,6), :period_end => Date.new(2014,8,12)) +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,17), :period_end => Date.new(2014,7,25)) +        subject.intersect! another_tt +        subject.reload +      end +      it "should have 0 period" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have no day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have date reduced for period" do +        expect(subject.dates.size).to eq(2) +        expect(subject.dates[0].date).to eq(Date.new(2014,7,18)) +        expect(subject.dates[1].date).to eq(Date.new(2014,7,19)) +      end +    end +    context "with only periods : intersect timetable have no one day period" do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,6)) +        subject.int_day_types = 4|8|16 +        another_tt = create(:time_table , :int_day_types => (4|8|16) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,6), :period_end => Date.new(2014,8,12)) +        subject.intersect! another_tt +        subject.reload +      end +      it "should have 0 result periods" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have no day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have 1 date " do +        expect(subject.dates.size).to eq(1) +        expect(subject.dates[0].date).to eq(Date.new(2014,8,6)) +      end +    end + +  end + +  describe "#disjoin!" do +    context "timetables have periods with common day_types " do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,6)) +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,20)) +        subject.int_day_types = 4|16|32|128 +        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,6), :period_end => Date.new(2014,8,12)) +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,15), :period_end => Date.new(2014,8,2)) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have 0 periods" do +        expect(subject.periods.size).to eq(0) +       end +      it "should have 0 day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have only dates " do +        expect(subject.dates.size).to eq(11) +        expect(subject.dates[0].date).to eq(Date.new(2014,6,30)) +        expect(subject.dates[1].date).to eq(Date.new(2014,7,2)) +        expect(subject.dates[2].date).to eq(Date.new(2014,7,3)) +        expect(subject.dates[3].date).to eq(Date.new(2014,7,5)) +        expect(subject.dates[4].date).to eq(Date.new(2014,7,7)) +        expect(subject.dates[5].date).to eq(Date.new(2014,7,9)) +        expect(subject.dates[6].date).to eq(Date.new(2014,7,10)) +        expect(subject.dates[7].date).to eq(Date.new(2014,7,12)) +        expect(subject.dates[8].date).to eq(Date.new(2014,7,14)) +        expect(subject.dates[9].date).to eq(Date.new(2014,7,17)) +        expect(subject.dates[10].date).to eq(Date.new(2014,8,4)) +      end +    end +    context "timetables have periods or dates " do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,17), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,19), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,8,6), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,8,7), :in_out => true) +        subject.int_day_types = 0 +        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,6), :period_end => Date.new(2014,8,12)) +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,17), :period_end => Date.new(2014,7,25)) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have 0 period" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have no remained day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have date reduced for period" do +        expect(subject.dates.size).to eq(4) +        expect(subject.dates[0].date).to eq(Date.new(2014,7,16)) +        expect(subject.dates[1].date).to eq(Date.new(2014,7,17)) +        expect(subject.dates[2].date).to eq(Date.new(2014,7,20)) +        expect(subject.dates[3].date).to eq(Date.new(2014,8,7)) +      end +    end +    context "disjoined timetable have all periods in removed ones " do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,8)) +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,20)) +        subject.int_day_types = 4|16|32|128 +        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,31), :period_end => Date.new(2014,8,12)) +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,20)) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have 0 result periods" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have no remained day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have dates for period reduced" do +        expect(subject.dates.size).to eq(4) +        expect(subject.dates[0].date).to eq(Date.new(2014,7,3)) +        expect(subject.dates[1].date).to eq(Date.new(2014,7,10)) +        expect(subject.dates[2].date).to eq(Date.new(2014,7,17)) +        expect(subject.dates[3].date).to eq(Date.new(2014,8,7)) +      end +    end + +    context "timetable with dates against timetable with dates and periods" do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,17), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,19), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true) +        subject.int_day_types = 0 +        another_tt = create(:time_table , :int_day_types => (4|16|64|128) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,7,31), :period_end => Date.new(2014,8,12)) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,17), :in_out => true) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have 0 result periods" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have no remained day_types" do +        subject.int_day_types == 0 +      end +      it "should have 3 dates left" do +        expect(subject.dates.size).to eq(3) +        expect(subject.dates[0].date).to eq(Date.new(2014,7,16)) +        expect(subject.dates[1].date).to eq(Date.new(2014,7,19)) +        expect(subject.dates[2].date).to eq(Date.new(2014,7,20)) +      end +    end +    context "timetable with dates against timetable with dates and periods all covered" do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,1), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,2), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,5), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,6), :in_out => true) +        subject.int_day_types = 512 +        another_tt = create(:time_table , :int_day_types => (32|64|512) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,11)) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,1), :in_out => true) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,2), :in_out => true) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,5), :in_out => true) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,6), :in_out => true) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have 0 result periods" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have no remained day_types" do +        subject.int_day_types == 0 +      end +      it "should have 0 dates left" do +        expect(subject.dates.size).to eq(0) +      end +    end + +    context "with only periods : disjoined timetable have no empty period" do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,8)) +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,10), :period_end => Date.new(2014,8,31)) +        subject.int_day_types = 4|8 +        another_tt = create(:time_table , :int_day_types => (4|8) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,4), :period_end => Date.new(2014,8,7)) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have 0 result periods" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have 0 day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have 6 dates " do +        expect(subject.dates.size).to eq(6) +        expect(subject.dates[0].date).to eq(Date.new(2014,8,11)) +        expect(subject.dates[1].date).to eq(Date.new(2014,8,12)) +        expect(subject.dates[2].date).to eq(Date.new(2014,8,18)) +        expect(subject.dates[3].date).to eq(Date.new(2014,8,19)) +        expect(subject.dates[4].date).to eq(Date.new(2014,8,25)) +        expect(subject.dates[5].date).to eq(Date.new(2014,8,26)) +      end +    end + +    context "with only periods : disjoined timetable have no one day period" do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,6)) +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,10), :period_end => Date.new(2014,8,31)) +        subject.int_day_types = 4|8|16 +        another_tt = create(:time_table , :int_day_types => (4|8) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,4), :period_end => Date.new(2014,8,5)) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have 0 result periods" do +        expect(subject.periods.size).to eq(0) +     end +      it "should have 0 day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have 10 dates " do +        expect(subject.dates.size).to eq(10) +        expect(subject.dates[0].date).to eq(Date.new(2014,8,6)) +        expect(subject.dates[1].date).to eq(Date.new(2014,8,11)) +        expect(subject.dates[2].date).to eq(Date.new(2014,8,12)) +        expect(subject.dates[3].date).to eq(Date.new(2014,8,13)) +        expect(subject.dates[4].date).to eq(Date.new(2014,8,18)) +        expect(subject.dates[5].date).to eq(Date.new(2014,8,19)) +        expect(subject.dates[6].date).to eq(Date.new(2014,8,20)) +        expect(subject.dates[7].date).to eq(Date.new(2014,8,25)) +        expect(subject.dates[8].date).to eq(Date.new(2014,8,26)) +        expect(subject.dates[9].date).to eq(Date.new(2014,8,27)) +      end +    end + +    context "with periods against dates: disjoined timetable have no unused excluded date" do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,1), :period_end => Date.new(2014,8,8)) +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2014,8,10), :period_end => Date.new(2014,8,31)) +        subject.int_day_types = 4|8|16 +        another_tt = create(:time_table , :int_day_types => (0) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,8,4), :in_out => true) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,8,5), :in_out => true) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,8,7), :in_out => true) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have same 0 result periods" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have 0 day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have only 10 dates " do +        expect(subject.dates.size).to eq(10) +        expect(subject.dates[0].date).to eq(Date.new(2014,8,6)) +        expect(subject.dates[1].date).to eq(Date.new(2014,8,11)) +        expect(subject.dates[2].date).to eq(Date.new(2014,8,12)) +        expect(subject.dates[3].date).to eq(Date.new(2014,8,13)) +        expect(subject.dates[4].date).to eq(Date.new(2014,8,18)) +        expect(subject.dates[5].date).to eq(Date.new(2014,8,19)) +        expect(subject.dates[6].date).to eq(Date.new(2014,8,20)) +        expect(subject.dates[7].date).to eq(Date.new(2014,8,25)) +        expect(subject.dates[8].date).to eq(Date.new(2014,8,26)) +        expect(subject.dates[9].date).to eq(Date.new(2014,8,27)) +      end +    end +   +   +    context "with same definition : dsjointed timetable should be empty" do +      before do +        subject.periods.clear +        subject.dates.clear +        subject.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2015,6,1), :period_end => Date.new(2015,6,30)) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2015,6,16), :in_out => true) +        subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2015,6,22), :in_out => false) +        subject.int_day_types = 4|8|16|32|64|128|256 +        another_tt = create(:time_table , :int_day_types => ( 4|8|16|32|64|128|256) ) +        another_tt.periods.clear +        another_tt.dates.clear +        another_tt.periods << Chouette::TimeTablePeriod.new(:period_start => Date.new(2015,6,1), :period_end => Date.new(2015,6,30)) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2015,6,16), :in_out => true) +        another_tt.dates << Chouette::TimeTableDate.new( :date => Date.new(2015,6,22), :in_out => false) +        subject.disjoin! another_tt +        subject.reload +      end +      it "should have same 0 result periods" do +        expect(subject.periods.size).to eq(0) +      end +      it "should have 0 day_types" do +        expect(subject.int_day_types).to eq(0) +      end +      it "should have 0 dates " do +        expect(subject.dates.size).to eq(0) +      end +    end +  end + +  describe "#duplicate" do +      it "should be a copy of" do +        target=subject.duplicate +        expect(target.id).to be_nil +        expect(target.comment).to eq(I18n.t("activerecord.copy", name: subject.comment)) +        expect(target.objectid).to eq(subject.objectid+"_1") +        expect(target.int_day_types).to eq(subject.int_day_types) +        expect(target.dates.size).to eq(subject.dates.size) +        target.dates.each do |d| +          expect(d.time_table_id).to be_nil +        end +        expect(target.periods.size).to eq(subject.periods.size) +        target.periods.each do |p| +          expect(p.time_table_id).to be_nil +        end +      end +  end + +  describe "#tags" do +      it "should accept tags" do +        subject.tag_list = "toto, titi" +        subject.save +        subject.reload +        expect(Chouette::TimeTable.tag_counts.size).to eq(2) +        expect(subject.tag_list.size).to eq(2) +      end +  end + +end diff --git a/spec/models/chouette/timeband_spec.rb b/spec/models/chouette/timeband_spec.rb new file mode 100644 index 000000000..1f812a6e2 --- /dev/null +++ b/spec/models/chouette/timeband_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Chouette::Timeband, :type => :model do + +  describe '#create' do +    context 'when valid' do +      it { create(:timeband) } +    end + +    context 'when not valid' do +      it 'fails validation with end_time before start_time' do +        timeband = build(:timeband_invalid) +        expect(timeband).to be_invalid +      end +    end +  end + +end diff --git a/spec/models/chouette/transport_mode_spec.rb b/spec/models/chouette/transport_mode_spec.rb new file mode 100644 index 000000000..8f2b2eddb --- /dev/null +++ b/spec/models/chouette/transport_mode_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe Chouette::TransportMode, :type => :model do +   +  def mode(text_code = "test", numerical_code = nil) +    numerical_code ||= 1 if text_code == "test" +    Chouette::TransportMode.new(text_code, numerical_code) +  end + +  describe "#to_i" do +     +    it "should return numerical code" do +      expect(mode("test", 1).to_i).to eq(1) +    end + +  end + +  it "should return true to #test? when text code is 'test'" do +    expect(mode("test")).to be_test +  end + +  it "should be equal when text codes are identical" do +    expect(mode("test",1)).to eq(mode("test", 2)) +  end + +  describe ".new" do + +    it "should find numerical code from text code" do +      expect(mode("unknown").to_i).to eq(0) +    end + +    it "should find text code from numerical code" do +      expect(mode(0)).to be_unknown +    end + +    it "should accept another mode" do +      expect(Chouette::TransportMode.new(mode("test"))).to eq(mode("test")) +    end +     +  end + +  describe "#public_transport?" do +     +    it "should return false for interchange" do +      expect(mode("interchange")).not_to be_public_transport +    end + +    it "should return true for other modes" do +      expect(mode("unknown")).to be_public_transport +    end + +  end + +  describe ".all" do +     +    Chouette::TransportMode.definitions.each do |text_code, numerical_code| +      it "should include a TransportMode #{text_code}" do +        expect(Chouette::TransportMode.all).to include(Chouette::TransportMode.new(text_code)) +      end +    end + +  end + +end diff --git a/spec/models/chouette/trident_active_record_spec.rb b/spec/models/chouette/trident_active_record_spec.rb new file mode 100644 index 000000000..9728a2923 --- /dev/null +++ b/spec/models/chouette/trident_active_record_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' + +describe Chouette::TridentActiveRecord, :type => :model do + +  it { expect(Chouette::TridentActiveRecord.ancestors).to include(Chouette::ActiveRecord) } + +  subject { create(:time_table) } + +  describe "#uniq_objectid" do + +    it "should rebuild objectid" do +      tm = create(:time_table) +      tm.objectid = subject.objectid +      tm.uniq_objectid +      expect(tm.objectid).to eq(subject.objectid+"_1") +    end + +    it "should rebuild objectid" do +      tm = create(:time_table) +      tm.objectid = subject.objectid +      tm.uniq_objectid +      tm.save +      tm = create(:time_table) +      tm.objectid = subject.objectid +      tm.uniq_objectid +      expect(tm.objectid).to eq(subject.objectid+"_2") +    end + +  end + +  describe "#prepare_auto_columns" do + +    it "should left objectid" do +      tm = Chouette::TimeTable.new :comment => "merge1" , :objectid => "NINOXE:Timetable:merge1" +      tm.prepare_auto_columns +      expect(tm.objectid).to eq("NINOXE:Timetable:merge1") +    end + +    it "should add pending_id to objectid" do +      tm = Chouette::TimeTable.new :comment => "merge1" +      tm.prepare_auto_columns +      expect(tm.objectid.start_with?("NINOXE:Timetable:__pending_id__")).to be_truthy +    end + +    it "should set id to objectid" do +      tm = Chouette::TimeTable.new :comment => "merge1" +      tm.save +      expect(tm.objectid).to eq("NINOXE:Timetable:"+tm.id.to_s) +    end + +    it "should detect objectid conflicts" do +      tm = Chouette::TimeTable.new :comment => "merge1" +      tm.save +      tm.objectid = "NINOXE:Timetable:"+(tm.id+1).to_s +      tm.save +      tm = Chouette::TimeTable.new :comment => "merge1" +      tm.save +      expect(tm.objectid).to eq("NINOXE:Timetable:"+tm.id.to_s+"_1") +    end + +  end + +  describe "objectid" do + +    it "should build automatic objectid when empty" do +      g1 = Chouette::GroupOfLine.new( :name => "g1") +      g1.save +      expect(g1.objectid).to eq("NINOXE:GroupOfLine:"+g1.id.to_s) +    end + +    it "should build automatic objectid with fixed when only suffix given" do +      g1 = Chouette::GroupOfLine.new( :name => "g1") +      g1.objectid = "toto" +      g1.save +      expect(g1.objectid).to eq("NINOXE:GroupOfLine:toto") +    end +     +    it "should build automatic objectid with extension when already exists" do +      g1 = Chouette::GroupOfLine.new( :name => "g1") +      g1.save +      cnt = g1.id + 1 +      g1.objectid = "NINOXE:GroupOfLine:"+cnt.to_s +      g1.save +      g2 = Chouette::GroupOfLine.new( :name => "g2") +      g2.save +      expect(g2.objectid).to eq("NINOXE:GroupOfLine:"+g2.id.to_s+"_1") +    end +     +    it "should build automatic objectid with extension when already exists" do +      g1 = Chouette::GroupOfLine.new( :name => "g1") +      g1.save +      cnt = g1.id + 2 +      g1.objectid = "NINOXE:GroupOfLine:"+cnt.to_s +      g1.save +      g2 = Chouette::GroupOfLine.new( :name => "g2") +      g2.objectid = "NINOXE:GroupOfLine:"+cnt.to_s+"_1" +      g2.save +      g3 = Chouette::GroupOfLine.new( :name => "g3") +      g3.save +      expect(g3.objectid).to eq("NINOXE:GroupOfLine:"+g3.id.to_s+"_2") +    end +     +    it "should build automatic objectid when id cleared" do +      g1 = Chouette::GroupOfLine.new( :name => "g1") +      g1.objectid = "NINOXE:GroupOfLine:xxxx" +      g1.save +      g1.objectid = nil +      g1.save +      expect(g1.objectid).to eq("NINOXE:GroupOfLine:"+g1.id.to_s) +    end +  end + +end + + diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb new file mode 100644 index 000000000..f771e8286 --- /dev/null +++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' +require 'pp' + +describe Chouette::VehicleJourneyAtStop, :type => :model do +  let!(:vehicle_journey) { create(:vehicle_journey_odd)} +  subject { vehicle_journey.vehicle_journey_at_stops.first } + +  describe "#exceeds_gap?" do +    it "should return false if gap < 1.hour" do +      t1 = 1.minutes.ago +      t2 = 1.minutes.ago + 4.hour +      expect(subject.exceeds_gap?(t1, t2)).to be_truthy +    end +    it "should return false if gap > 2.hour" do +      t1 = 1.minutes.ago +      t2 = 1.minutes.ago + 3.minutes +      expect(subject.exceeds_gap?(t1, t2)).to be_falsey +    end +  end + +  describe "#increasing_times_validate" do +    let(:vjas1){ vehicle_journey.vehicle_journey_at_stops[0]} +    let(:vjas2){ vehicle_journey.vehicle_journey_at_stops[1]} +    context "when vjas#arrival_time exceeds gap" do +      it "should add errors on arrival_time" do +        vjas1.arrival_time = vjas2.arrival_time - 5.hour +        expect(vjas2.increasing_times_validate(vjas1)).to be_falsey +        expect(vjas2.errors).not_to be_empty +        expect(vjas2.errors[:arrival_time]).not_to be_blank +      end +    end +    context "when vjas#departure_time exceeds gap" do +      it "should add errors on departure_time" do +        vjas1.departure_time = vjas2.departure_time - 5.hour +        expect(vjas2.increasing_times_validate(vjas1)).to be_falsey +        expect(vjas2.errors).not_to be_empty +        expect(vjas2.errors[:departure_time]).not_to be_blank +      end +    end +    context "when vjas does'nt exceed gap" do +      it "should not add errors" do +        expect(vjas2.increasing_times_validate(vjas1)).to be_truthy +        expect(vjas2.errors).to be_empty +      end +    end +  end +end  diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb new file mode 100644 index 000000000..87c0f9d92 --- /dev/null +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -0,0 +1,223 @@ +require 'spec_helper' + +describe Chouette::VehicleJourney, :type => :model do +  subject { create(:vehicle_journey_odd) } + +  describe "in_relation_to_a_journey_pattern methods" do +    let!(:route) { create(:route)} +    let!(:journey_pattern) { create(:journey_pattern, :route => route)} +    let!(:journey_pattern_odd) { create(:journey_pattern_odd, :route => route)} +    let!(:journey_pattern_even) { create(:journey_pattern_even, :route => route)} + +    context "when vehicle_journey is on odd stop whereas selected journey_pattern is on all stops" do +      subject { create(:vehicle_journey, :route => route, :journey_pattern => journey_pattern_odd)} +      describe "#extra_stops_in_relation_to_a_journey_pattern" do +        it "should be empty" do +          expect(subject.extra_stops_in_relation_to_a_journey_pattern( journey_pattern)).to be_empty +        end +      end +      describe "#extra_vjas_in_relation_to_a_journey_pattern" do +        it "should be empty" do +          expect(subject.extra_vjas_in_relation_to_a_journey_pattern( journey_pattern)).to be_empty +        end +      end +      describe "#missing_stops_in_relation_to_a_journey_pattern" do +        it "should return even stops" do +          result = subject.missing_stops_in_relation_to_a_journey_pattern( journey_pattern) +          expect(result).to eq(journey_pattern_even.stop_points) +        end +      end +      describe "#update_journey_pattern" do +        it "should new_record for added vjas" do +          subject.update_journey_pattern( journey_pattern) +          subject.vehicle_journey_at_stops.select{ |vjas| vjas.new_record? }.each do |vjas| +            expect(journey_pattern_even.stop_points).to include( vjas.stop_point) +          end +        end +        it "should add vjas on each even stops" do +          subject.update_journey_pattern( journey_pattern) +          vehicle_stops = subject.vehicle_journey_at_stops.map(&:stop_point) +          journey_pattern_even.stop_points.each do |sp| +            expect(vehicle_stops).to include(sp) +          end +        end +        it "should not mark any vjas as _destroy" do +          subject.update_journey_pattern( journey_pattern) +          expect(subject.vehicle_journey_at_stops.any?{ |vjas| vjas._destroy }).to be_falsey +        end +      end +    end +    context "when vehicle_journey is on all stops whereas selected journey_pattern is on odd stops" do +      subject { create(:vehicle_journey, :route => route, :journey_pattern => journey_pattern)} +      describe "#missing_stops_in_relation_to_a_journey_pattern" do +        it "should be empty" do +          expect(subject.missing_stops_in_relation_to_a_journey_pattern( journey_pattern_odd)).to be_empty +        end +      end +      describe "#extra_stops_in_relation_to_a_journey_pattern" do +        it "should return even stops" do +          result = subject.extra_stops_in_relation_to_a_journey_pattern( journey_pattern_odd) +          expect(result).to eq(journey_pattern_even.stop_points) +        end +      end +      describe "#extra_vjas_in_relation_to_a_journey_pattern" do +        it "should return vjas on even stops" do +          result = subject.extra_vjas_in_relation_to_a_journey_pattern( journey_pattern_odd) +          expect(result.map(&:stop_point)).to eq(journey_pattern_even.stop_points) +        end +      end +      describe "#update_journey_pattern" do +        it "should add no new vjas" do +          subject.update_journey_pattern( journey_pattern_odd) +          expect(subject.vehicle_journey_at_stops.any?{ |vjas| vjas.new_record? }).to be_falsey +        end +        it "should mark vehicle_journey_at_stops as _destroy on even stops" do +          subject.update_journey_pattern( journey_pattern_odd) +          subject.vehicle_journey_at_stops.each { |vjas| +            expect(vjas._destroy).to eq(journey_pattern_even.stop_points.include?(vjas.stop_point)) +          } +        end +      end +    end + +  end +  context "when following departure times exceeds gap" do +    describe "#increasing_times" do +      before(:each) do +        subject.vehicle_journey_at_stops[0].departure_time = subject.vehicle_journey_at_stops[1].departure_time - 5.hour +        subject.vehicle_journey_at_stops[0].arrival_time = subject.vehicle_journey_at_stops[0].departure_time +        subject.vehicle_journey_at_stops[1].arrival_time = subject.vehicle_journey_at_stops[1].departure_time +      end +      it "should make instance invalid" do +        subject.increasing_times +        expect(subject.vehicle_journey_at_stops[1].errors[:departure_time]).not_to be_blank +        expect(subject).not_to be_valid +      end +    end +    describe "#update_attributes" do +      let!(:params){ {"vehicle_journey_at_stops_attributes" => { +            "0"=>{"id" => subject.vehicle_journey_at_stops[0].id ,"arrival_time" => 1.minutes.ago,"departure_time" => 1.minutes.ago}, +            "1"=>{"id" => subject.vehicle_journey_at_stops[1].id, "arrival_time" => (1.minutes.ago + 4.hour),"departure_time" => (1.minutes.ago + 4.hour)} +         }}} +      it "should return false" do +        expect(subject.update_attributes(params)).to be_falsey +      end +      it "should make instance invalid" do +        subject.update_attributes(params) +        expect(subject).not_to be_valid +      end +      it "should let first vjas without any errors" do +        subject.update_attributes(params) +        expect(subject.vehicle_journey_at_stops[0].errors).to be_empty +      end +      it "should add an error on second vjas" do +        subject.update_attributes(params) +        expect(subject.vehicle_journey_at_stops[1].errors[:departure_time]).not_to be_blank +      end +    end +  end + +  context "#time_table_tokens=" do +    let!(:tm1){create(:time_table, :comment => "TM1")} +    let!(:tm2){create(:time_table, :comment => "TM2")} + +    it "should return associated time table ids" do +      subject.update_attributes :time_table_tokens => [tm1.id, tm2.id].join(',') +      expect(subject.time_tables).to include( tm1) +      expect(subject.time_tables).to include( tm2) +    end +  end +  describe "#bounding_dates" do +    before(:each) do +      tm1 = build(:time_table, :dates => +                               [ build(:time_table_date, :date => 1.days.ago.to_date, :in_out => true), +                                 build(:time_table_date, :date => 2.days.ago.to_date, :in_out => true) ]) +      tm2 = build(:time_table, :periods => +                                [ build(:time_table_period, :period_start => 4.days.ago.to_date, :period_end => 3.days.ago.to_date)]) +      tm3 = build(:time_table) +      subject.time_tables = [ tm1, tm2, tm3] +    end +    it "should return min date from associated calendars" do +      expect(subject.bounding_dates.min).to eq(4.days.ago.to_date) +    end +    it "should return max date from associated calendars" do +      expect(subject.bounding_dates.max).to eq(1.days.ago.to_date) +    end +  end +  context "#vehicle_journey_at_stops" do +    it "should be ordered like stop_points on route" do +      route = subject.route +      vj_stop_ids = subject.vehicle_journey_at_stops.map(&:stop_point_id) +      expected_order = route.stop_points.map(&:id).select {|s_id| vj_stop_ids.include?(s_id)} + +      expect(vj_stop_ids).to eq(expected_order) +    end + +  end + +    describe "#transport_mode_name" do + +    def self.legacy_transport_modes +      %w{Air Train LongDistanceTrain LocalTrain RapidTransit Metro Tramway Coach Bus Ferry Waterborne PrivateVehicle Walk Trolleybus Bicycle Shuttle Taxi VAL Other} +    end + +    legacy_transport_modes.each do |transport_mode| +      context "when transport_mode is #{transport_mode}" do +        transport_mode_name = Chouette::TransportMode.new(transport_mode.underscore) +        it "should be #{transport_mode_name}" do +          subject.transport_mode = transport_mode +          expect(subject.transport_mode_name).to eq(transport_mode_name) +        end +      end +    end +    context "when transport_mode is nil" do +      it "should be nil" do +        subject.transport_mode = nil +        expect(subject.transport_mode_name).to be_nil +      end +    end + +  end + +  describe "#transport_mode_name=" do + +    it "should change transport_mode with TransportMode#name" do +      subject.transport_mode_name = "Test" +      expect(subject.transport_mode).to eq("Test") +    end + +  end + +  describe ".transport_mode_names" do + +    it "should not include unknown transport_mode_name" do +      expect(Chouette::VehicleJourney.transport_mode_names).not_to include(Chouette::TransportMode.new("unknown")) +    end + +    it "should not include interchange transport_mode" do +      expect(Chouette::VehicleJourney.transport_mode_names).not_to include(Chouette::TransportMode.new("interchange")) +    end + +  end + +  describe "#footnote_ids=" do +    context "when line have footnotes, " do +      let!( :route) { create( :route ) } +      let!( :line) { route.line } +      let!( :footnote_first) {create( :footnote, :code => "1", :label => "dummy 1", :line => route.line)} +      let!( :footnote_second) {create( :footnote, :code => "2", :label => "dummy 2", :line => route.line)} + + +      it "should update vehicle's footnotes" do +        expect(Chouette::VehicleJourney.find(subject.id).footnotes).to be_empty +        subject.footnote_ids = [ footnote_first.id ] +        subject.save +        expect(Chouette::VehicleJourney.find(subject.id).footnotes.count).to eq(1) +      end + +    end + +  end + +end + diff --git a/spec/presenters/chouette/geometry/general_presenter.rb b/spec/presenters/chouette/geometry/general_presenter.rb new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/spec/presenters/chouette/geometry/general_presenter.rb @@ -0,0 +1 @@ + diff --git a/spec/presenters/chouette/geometry/line_presenter_spec.rb b/spec/presenters/chouette/geometry/line_presenter_spec.rb new file mode 100644 index 000000000..c1432cf57 --- /dev/null +++ b/spec/presenters/chouette/geometry/line_presenter_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Chouette::Geometry::LinePresenter do +  let!(:line) { create(:line_with_stop_areas_having_parent) } +  subject { Chouette::Geometry::LinePresenter.new(line)} + +  describe "#routes_localized_commercials" do +    it "should return 3 stop_areas" do +      expect(subject.routes_localized_commercials(line.routes.first).size).to eq(10) +    end +  end +end + | 
