app/models/export/netex_generic.rb
class Export::NetexGeneric < Export::Base
include LocalExportSupport
option :profile, enumerize: %w[none french european idfm/iboo idfm/icar idfm/full], default: :none
option :duration
option :from, serialize: ActiveModel::Type::Date
option :to, serialize: ActiveModel::Type::Date
option :line_ids, serialize: :map_ids
option :company_ids, serialize: :map_ids
option :line_provider_ids, serialize: :map_ids
option :period, default_value: 'all_periods', enumerize: %w[all_periods only_next_days static_day_period]
option :exported_lines, default_value: 'all_line_ids', enumerize: %w[line_ids company_ids line_provider_ids all_line_ids]
option :participant_ref, default_value: 'enRoute'
option :profile_options, default_value: '{}', serialize: ActiveRecord::Type::Json
option :prefer_referent_line, default_value: false, enumerize: [true, false], serialize: ActiveModel::Type::Boolean
option :exported_code_space
validate :ensure_is_valid_period
def ensure_is_valid_period
return unless period == 'static_day_period'
if from.blank? || to.blank? || from > to
errors.add(:from, :invalid)
errors.add(:to, :invalid)
end
end
def target
@target ||= ::Netex::Target.build(export_file,
profile: netex_profile,
publication_timestamp: Time.zone.now,
participant_ref: participant_ref,
validity_periods: [export_scope.validity_period],
profile_options: profile_options)
end
attr_writer :target
def profile?
! [nil, 'none'].include? profile
end
def netex_profile
@netex_profile ||= Netex::Profile.create(profile) if profile?
end
def content_type
profile? ? 'application/zip' : 'text/xml'
end
def file_extension
profile? ? "zip" : 'xml'
end
delegate :stop_area_referential, :line_referential, to: :workgroup
class Scope < SimpleDelegator
def initialize(export_scope, export:)
super export_scope
@export = export
end
attr_reader :export
delegate :stop_area_referential, :line_referential, to: :export
def current_scope
__getobj__
end
def stop_areas
@stop_areas ||=
::Query::StopArea.new(stop_area_referential.stop_areas).
self_referents_and_ancestors(current_scope.stop_areas)
end
def entrances
# Must unscope the entrances to find entrances associated with all exported Stop Areas
# (including parent Stop Areas)
stop_area_referential.entrances.where(stop_area: stop_areas)
end
def lines_class
prefer_referent_lines? ? Lines::PreferReferents : Lines::Default
end
def lines
@lines ||= lines_class.new(self).lines
# prefer_referent_lines? ? lines_or_referents : lines_and_referents
end
module Lines
class Base
def initialize(export_scope)
@export_scope = export_scope
end
attr_reader :export_scope
delegate :line_referential, :current_scope, to: :export_scope
def all_lines
line_referential.lines
end
def original_scoped_lines
current_scope.lines
end
end
class Default < Base
def lines_and_referents
::Query::Line.new(all_lines).self_and_referents(original_scoped_lines)
end
alias lines lines_and_referents
end
class PreferReferents < Base
def lines_or_referents
line_referential.lines.where(id: lines_or_referent_ids)
end
alias lines lines_or_referents
def lines_or_referent_ids
[
original_scoped_lines.without_referent,
all_lines.where(id: original_scoped_lines.with_referent.select(:referent_id))
].flat_map { |relation| relation.pluck(:id) }.uniq
end
end
end
def prefer_referent_lines?
export.prefer_referent_line
end
def referenced_lines
if prefer_referent_lines?
current_scope.lines.particulars.with_referent
else
Chouette::Line.none
end
end
end
def export_scope
@local_export_scope ||= Scope.new(super, export: self)
end
def resource_tagger
@resource_tagger ||= ResourceTagger.new(code_provider: code_provider)
end
def export_file
@export_file ||= Tempfile.new(["export#{id}",'.zip'])
end
def generate_export_file
part_classes = [
Entrances,
Quays,
StopPlaces,
Companies,
Networks,
LineNotices,
Lines,
# Export StopPoints before Routes to detect local references
StopPoints,
Routes,
RoutingConstraintZones,
JourneyPatterns,
TimeTables,
VehicleJourneyAtStops,
VehicleJourneys,
VehicleJourneyStopAssignments,
Organisations,
PointOfInterests
]
part_classes.each_with_index do |part_class, index|
part_class.new(self).export_part
end
Finalizer.new(self).export_part
export_file.close
export_file
end
class TaggedTarget
def initialize(target, tags = {})
@target = target
@tags = tags
end
def add(resource)
resource.tags = @tags
@target << resource
end
alias << add
end
class Part
attr_reader :export
def initialize(export, options = {})
@export = export
options.each { |k,v| send "#{k}=", v }
end
delegate :target, :resource_tagger, :export_scope, :workgroup,
:alternate_identifiers_extractor, :code_provider, to: :export
def internal_description
@internal_description ||= self.class.name.demodulize.underscore
end
def logger
Rails.logger
end
include Operation::CallbackSupport
class Benchmarker < Operation::Callback
delegate :internal_description, to: :operation
def around(&block)
Chouette::Benchmark.measure(internal_description, &block)
end
end
callback Operation::LogTagger
callback Benchmarker
callback Operation::Bullet if defined?(::Bullet) # By waiting Export as real Operation
callback Operation::StackProf if defined?(::StackProf)
include AroundMethod
around_method :export_part
def around_export_part(&block)
Operation::Callback::Invoker.new(callbacks) do
block.call
end.call
end
def decorate(model, **attributes)
decorator_class = attributes.delete(:with) || default_decorator_class
attributes = attributes.merge(
alternate_identifiers_extractor: alternate_identifiers_extractor,
code_provider: code_provider
)
decorator_class.new model, **attributes
end
def default_decorator_class
@decorator_class ||= self.class.const_get('Decorator')
end
end
class Finalizer < Part
def export_part
target.close
end
end
class ResourceTagger
def initialize(code_provider: nil)
@code_provider = code_provider
end
def code_provider
@code_provider ||= Export::CodeProvider.null
end
# Returns tags for several lines.
# Returns only uniq values accross all given lines
def tags_for_lines line_ids
tags = Hash.new { |h,k| h[k] = Set.new }
line_ids.each do |line_id|
tags_for(line_id).each do |key, value|
tags[key] << value
end
end
# Remove multiple values
tags.map do |key, set|
[ key, set.first ] if set.size == 1
end.compact.to_h
end
def tags_for line_id
line_id = aliases.fetch(line_id, line_id)
tag_index[line_id]
end
def register_tags_for(line)
tag_index[line.id] = {
line_id: code_provider.lines.code(line.id),
line_name: line.name,
operator_id: code_provider.companies.code(line.company_id),
operator_name: line.company&.name
}.compact
end
def alias_tags_for(line_id, as:)
aliases[line_id] = as
end
protected
def aliases
@aliases ||= {}
end
def tag_index
@tag_index ||= Hash.new { |h,k| h[k] = {} }
end
end
def alternate_identifiers_extractor
@alternate_identifiers_extractor ||= AlternateIdentifiersExtractor.new(workgroup&.code_spaces || [])
end
class AlternateIdentifiersExtractor
def initialize(code_spaces)
@code_spaces = code_spaces.map do |code_space|
[ code_space.id, code_space.short_name ]
end.to_h
end
attr_reader :code_spaces
def decorate(model)
Decorator.new(model, code_spaces: code_spaces)
end
class Decorator
def initialize(model, code_spaces: {})
@model = model
@code_spaces = code_spaces
end
attr_reader :model, :code_spaces
delegate :registration_number, :codes, to: :model
def registration_number_value
if has_registration_number?
[[ "external", registration_number ]]
else
[]
end
end
def has_registration_number?
model.respond_to?(:registration_number) && registration_number.present?
end
def has_codes?
model.respond_to? :codes
end
def codes_values
if has_codes?
codes.map do |code|
code_space_short_name = code_spaces[code.code_space_id]
[ code_space_short_name, code.value ] if code_space_short_name
end.compact
else
[]
end
end
def alternate_identifiers_values
registration_number_value + codes_values
end
def alternate_identifiers
alternate_identifiers_values.map do |key, value|
Netex::KeyValue.new key: key, value: value, type_of_key: "ALTERNATE_IDENTIFIER"
end
end
end
end
class CustomFieldExtractor
def initialize(model)
@model = model
end
attr_reader :model
delegate :custom_field_values, to: :model, allow_nil: true
def custom_field_identifiers
return [] unless custom_field_values.present?
custom_field_values.map do |key, value|
Netex::KeyValue.new key: key, value: value, type_of_key: "chouette::custom-field"
end
end
end
module Accessibility
def accessibility_assessment
return unless accessibility_assessment?
Netex::AccessibilityAssessment.new(
id: netex_identifier&.change(type: 'AccessibilityAssessment').to_s,
mobility_impaired_access: netex_value(mobility_impaired_accessibility),
limitations: [accessibility_limitation].compact,
validity_conditions: [availability_condition].compact
)
end
def accessibility_limitation
return unless accessibility_limitation?
Netex::AccessibilityLimitation.new(
wheelchair_access: netex_value(wheelchair_accessibility),
step_free_access: netex_value(step_free_accessibility),
escalator_free_access: netex_value(escalator_free_accessibility),
lift_free_access: netex_value(lift_free_accessibility),
audible_signals_available: netex_value(audible_signals_availability),
visual_signs_available: netex_value(visual_signs_availability)
)
end
def netex_value(value)
case value
when 'yes'
'true'
when 'no'
'false'
else
value
end
end
def availability_condition
return unless accessibility_limitation_description.present?
Netex::AvailabilityCondition.new(
id: netex_identifier.change(type: 'AvailabilityCondition').to_s,
description: accessibility_limitation_description
)
end
def accessibility_assessment?
accessibility_limitation? || availability_condition.present? || mobility_impaired_accessibility != 'unknown'
end
def accessibility_limitation?
%i[
wheelchair_accessibility step_free_accessibility escalator_free_accessibility
lift_free_accessibility audible_signals_availability visual_signs_availability
].any? do |attribute|
send(attribute) != :unknown
end
end
end
class ModelDecorator < SimpleDelegator
def initialize(model, **attributes)
super model
attributes.each { |k,v| send "#{k}=", v }
end
def model
__getobj__
end
attr_writer :alternate_identifiers_extractor
def alternate_identifiers_extractor
@alternate_identifiers_extractor ||= AlternateIdentifiersExtractor.new([])
end
def netex_alternate_identifiers
alternate_identifiers_extractor.decorate(model).alternate_identifiers
end
attr_writer :code_provider
def netex_identifier
@netex_identifier ||= Code::Value.parse(code_provider.code(model))
end
def netex_attributes
{
id: netex_identifier.to_s,
changed: updated_at,
created: created_at
}
end
def code_provider
@code_provider ||= Export::CodeProvider.null
end
def decorate(model, **attributes)
decorator_class = attributes.delete(:with) || default_decorator_class
attributes = attributes.merge(
alternate_identifiers_extractor: alternate_identifiers_extractor,
code_provider: code_provider
)
decorator_class.new model, **attributes
end
end
class StopDecorator < ModelDecorator
include Accessibility
def netex_attributes # rubocop:disable Metrics/MethodLength
super.merge(
{
derived_from_object_ref: derived_from_object_ref,
name: name,
public_code: public_code,
private_code: private_code,
centroid: centroid,
raw_xml: import_xml,
key_list: key_list,
accessibility_assessment: accessibility_assessment,
postal_address: postal_address,
url: url,
transport_mode: netex_transport_mode,
transport_submode: netex_transport_submode
}.tap do |attributes|
unless netex_quay?
attributes[:parent_site_ref] = parent_site_ref
attributes[:place_types] = place_types
end
end
)
end
def netex_transport_mode
transport_mode&.camelize_mode
end
def netex_transport_submode
transport_mode&.camelize_sub_mode
end
def parent_objectid
@parent_objectid ||= code_provider.stop_areas.code(parent_id)
end
def derived_from_object_ref
code_provider.stop_areas.code(referent_id)
end
def key_list
netex_alternate_identifiers + netex_custom_field_identifiers
end
def netex_custom_field_identifiers
CustomFieldExtractor.new(self).custom_field_identifiers
end
def centroid
Netex::Point.new(location: Netex::Location.new(longitude: longitude, latitude: latitude))
end
def parent_site_ref
Netex::Reference.new(parent_objectid, type: 'StopPlace') if parent_objectid
end
def place_types
[Netex::Reference.new(type_of_place, type: String)]
end
def type_of_place
case area_type
when Chouette::AreaType::QUAY
'quay'
when 'zdlp'
'monomodalStopPlace'
when 'lda'
'generalStopPlace'
when 'gdl'
'groupOfStopPlaces'
end
end
def postal_address_objectid
netex_identifier&.change(type: 'PostalAddress').to_s
end
def postal_address
Netex::PostalAddress.new(
id: postal_address_objectid,
address_line_1: street_name,
post_code: zip_code,
town: city_name,
postal_region: postal_region,
country_name: country_name
)
end
def netex_resource
netex_resource_class.new(netex_attributes).tap do |stop|
if netex_quay?
stop.with_tag parent_id: parent_objectid
end
end
end
def netex_quay?
area_type&.to_sym == Chouette::AreaType::QUAY
end
def netex_resource_class
netex_quay? ? Netex::Quay : Netex::StopPlace
end
def private_code
registration_number
end
end
class Quays < Part
delegate :stop_areas, to: :export_scope
def export_part
stop_areas.where(area_type: Chouette::AreaType::QUAY).includes(:codes).find_each do |stop_area|
netex_resource = decorate(stop_area, with: StopDecorator).netex_resource
target << netex_resource
end
end
end
class StopPlaces < Part
delegate :stop_areas, to: :export_scope
def export_part
stop_areas.where.not(area_type: Chouette::AreaType::QUAY).includes(:codes, :entrances).find_each do |stop_area|
stop_place = decorate(stop_area, with: StopDecorator).netex_resource
target << stop_place
end
end
end
class Entrances < Part
delegate :entrances, to: :export_scope
def export_part
entrances.includes(:raw_import).find_each do |entrance|
decorated_entrance = decorate(entrance)
target << decorated_entrance.netex_resource
end
end
class Decorator < ModelDecorator
def netex_attributes
super.merge(
name: name,
short_name: short_name,
description: description,
centroid: centroid,
postal_address: postal_address,
entrance_type: entrance_type,
is_entry: entry?,
is_exit: exit?,
raw_xml: raw_xml,
)
end
def netex_resource
Netex::StopPlaceEntrance.new(netex_attributes).with_tag(parent_id: parent_objectid)
end
def centroid
Netex::Point.new(
location: Netex::Location.new(longitude: longitude, latitude: latitude)
)
end
def postal_address_objectid
netex_identifier&.change(type: 'PostalAddress').to_s
end
def postal_address
Netex::PostalAddress.new(
id: postal_address_objectid,
address_line_1: address_line_1,
post_code: zip_code,
town: city_name,
country_name: country
)
end
def parent_objectid
stop_area&.objectid
end
def raw_xml
raw_import&.content
end
end
end
class PointOfInterests < Part
def export_part
point_of_interests.find_each do |point_of_interest|
decorated_point_of_interest = decorate(point_of_interest)
target << decorated_point_of_interest.netex_resource
end
end
def point_of_interests
export_scope.point_of_interests
.includes(:codes, :point_of_interest_hours)
.joins(:point_of_interest_category)
.select(
"point_of_interests.*",
"point_of_interest_categories.name AS category_name"
)
end
class Decorator < ModelDecorator
def netex_attributes
{
id: uuid,
name: name,
url: url,
centroid: centroid,
postal_address: postal_address,
key_list: netex_alternate_identifiers,
operating_organisation_view: operating_organisation_view,
classifications: classifications,
validity_conditions: validity_conditions,
}
end
def netex_resource
Netex::PointOfInterest.new(netex_attributes)
end
def centroid
return unless longitude || latitude
Netex::Point.new(
location: Netex::Location.new(longitude: longitude, latitude: latitude)
)
end
def postal_address
Netex::PostalAddress.new(
id: "Address:#{uuid}",
address_line_1: address_line_1,
post_code: zip_code,
town: city_name,
postal_region: postal_region,
country_name: country
)
end
def operating_organisation_view
Netex::OperatingOrganisationView.new(
contact_details: Netex::ContactDetails.new(
phone: phone,
email: email
)
)
end
def classifications
[ Netex::PointOfInterestClassificationView.new(name: category_name) ]
end
def validity_conditions
[].tap do |validity_conditions|
point_of_interest_hours.find_each do |hour|
validity_condition = ValidityCondition.new(hour, uuid)
validity_conditions << Netex::AvailabilityCondition.new(
day_types: validity_condition.day_types,
timebands: validity_condition.timebands
)
end
end
end
class ValidityCondition
def initialize(hour, uuid)
@hour = hour
@uuid = uuid
end
attr_accessor :hour, :uuid
def timebands
[
Netex::Timeband.new(
id: id,
start_time: start_time,
end_time: end_time
)
]
end
def day_types
[
Netex::DayType.new(
id: id,
properties: properties
)
]
end
private
def id
"#{uuid}-#{hour.id}"
end
def start_time
hour.opening_time_of_day.to_hms
end
def end_time
hour.closing_time_of_day.to_hms
end
def properties
[ Netex::PropertyOfDay.new(days_of_week: days_of_week) ]
end
def days_of_week
all_days
.select{ |day| contains_day?(day) }
.map{ |day| day.to_s.capitalize }
.join(' ')
end
def all_days
@all_days ||= Cuckoo::Timetable::DaysOfWeek.all.days
end
def contains_day?(day)
hour.week_days.send("#{day}?")
end
end
end
end
class Lines < Part
delegate :lines, to: :export_scope
def export_part
export_scope.referenced_lines.pluck(:id, :referent_id).each do |line_id, referent_id|
code_provider.lines.alias(line_id, as: referent_id)
resource_tagger.alias_tags_for line_id, as: referent_id
end
lines.includes(:company, :codes).find_each do |line|
resource_tagger.register_tags_for line
tags = resource_tagger.tags_for(line.id)
tagged_target = TaggedTarget.new(target, tags)
decorated_line = decorate(line)
tagged_target << decorated_line.netex_resource
end
end
class Decorator < ModelDecorator
include Accessibility
def netex_attributes
super.merge(
name: netex_name,
transport_mode: netex_transport_mode,
transport_submode: netex_transport_submode,
operator_ref: operator_ref,
public_code: number,
represented_by_group_ref: represented_by_group_ref,
presentation: presentation,
additional_operators: additional_operators,
key_list: netex_alternate_identifiers,
accessibility_assessment: accessibility_assessment,
status: status,
valid_between: valid_between,
raw_xml: import_xml,
derived_from_object_ref: derived_from_object_ref
)
end
def netex_name
name || published_name
end
def valid_between
return unless active_from.present? || active_until.present?
from_date = active_from.present? ? active_from.beginning_of_day : nil
to_date = active_until.present? ? (active_until + 1).beginning_of_day : nil
Netex::ValidBetween.new(
from_date: from_date,
to_date: to_date
)
end
def derived_from_object_ref
code_provider.lines.code(referent_id)
end
def additional_operators
return unless secondary_company_ids
secondary_company_ids.map do |company_id|
company_code = code_provider.companies.code(company_id)
Netex::Reference.new(company_code, type: 'OperatorRef') if company_code
end.compact
end
def status
deactivated ? 'inactive' : ''
end
def netex_transport_mode
transport_mode == 'telecabin' ? 'cableway' : transport_mode
end
def netex_transport_submode
transport_submode&.to_s unless transport_submode == :undefined
end
def presentation
Netex::Presentation.new(text_colour: text_color&.downcase, colour: color&.downcase)
end
def netex_resource
Netex::Line.new netex_attributes
end
def operator_ref
company_code = code_provider.companies.code(company_id) if code_provider
Netex::Reference.new(company_code, type: 'OperatorRef') if company_code
end
def represented_by_group_ref
network_code = code_provider.networks.code(network_id) if code_provider
Netex::Reference.new(network_code, type: 'NetworkRef') if network_code
end
end
end
class LineNotices < Part
delegate :line_notices, to: :export_scope
def export_part
line_notices.includes(:codes).find_each do |line_notice|
target << decorate(line_notice).netex_resource
end
end
class Decorator < ModelDecorator
def netex_attributes
super.merge(
name: title,
text: content,
type_of_notice_ref: Netex::Reference.new('LineNotice', type: String),
raw_xml: import_xml,
key_list: netex_alternate_identifiers
)
end
def netex_resource
Netex::Notice.new netex_attributes
end
end
end
class Companies < Part
delegate :companies, to: :export_scope
def export_part
companies.includes(:codes).find_each do |company|
decorated_company = decorate(company)
target << decorated_company.netex_resource
end
end
class Decorator < ModelDecorator
def netex_attributes
super.merge(
name: name,
raw_xml: import_xml,
key_list: netex_alternate_identifiers
)
end
def netex_resource
Netex::Operator.new netex_attributes
end
end
end
class Networks < Part
delegate :networks, to: :export_scope
def export_part
networks.find_each do |network|
decorated_network = decorate(network)
target << decorated_network.netex_resource
end
end
class Decorator < ModelDecorator
def netex_attributes
super.merge(
name: name,
raw_xml: import_xml
)
end
def netex_resource
Netex::Network.new netex_attributes
end
end
end
module StopPointDecorator
class Base < SimpleDelegator
def initialize(stop_point, **attributes)
super stop_point
attributes.each do |k,v|
writer_method = "#{k}="
send writer_method, v if respond_to?(writer_method)
end
end
def stop_point
__getobj__
end
def netex_identifier
# Use stop_points.code to support more easily PseudoStopPoint
@netex_identifier ||= Code::Value.parse(stop_point_code)
end
def stop_point_code
code_provider.stop_points.code(stop_point.id)
end
attr_writer :code_provider
def code_provider
@code_provider ||= Export::CodeProvider.null
end
def netex_order
position+1
end
attr_writer :data_source_ref
def data_source_ref
stop_point.try(:data_source_ref) || @data_source_ref
end
def decorate(with:)
with.new(stop_point, code_provider: code_provider)
end
end
# <Route ...>
# <pointsInSequence>
# <PointOnRoute id="chouette:PointOnRoute:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any" order="1">
# <RoutePointRef ref="chouette:RoutePoint:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any"/>
# </PointOnRoute>
# <PointOnRoute id="chouette:PointOnRoute:95c5bfc9-62f9-4490-9a57-3fa2fd6c3013:LOC" version="any" order="2">
# <RoutePointRef ref="chouette:RoutePoint:95c5bfc9-62f9-4490-9a57-3fa2fd6c3013:LOC" version="any"/>
# </PointOnRoute>
# </pointsInSequence>
# </Route>
class PointOnRoute < Base
def point_on_route
Netex::PointOnRoute.new point_on_route_attributes
end
def netex_identifier
@netex_identifier ||= super.change(type: 'PointOnRoute')
end
def point_on_route_attributes
{
id: netex_identifier.to_s,
order: netex_order,
route_point_ref: route_point_ref
}
end
def route_point_ref
decorate(with: RoutePoint).route_point_ref
end
end
# Create a RoutePoint or a RoutePointRef from a Chouette::StopPoint
#
# <RoutePoint id="chouette:RoutePoint:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any">
# <projections>
# <PointProjection id="chouette:PointProjection:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any">
# <ProjectToPointRef ref="chouette:ScheduledStopPoint:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any"/>
# </PointProjection>
# </projections>
# </RoutePoint>
class RoutePoint < Base
def route_point
Netex::RoutePoint.new(route_point_attributes)
end
def route_point_attributes
{
id: netex_identifier.to_s,
projections: point_projection,
data_source_ref: data_source_ref
}
end
def netex_identifier
@netex_identifier ||= super.change(type: 'RoutePoint')
end
def route_point_ref
Netex::Reference.new(netex_identifier.to_s, type: 'RoutePointRef')
end
def point_projection
[Netex::PointProjection.new(point_projection_attributes)]
end
def point_projection_attributes
{
id: point_projection_id,
project_to_point_ref: scheduled_stop_point_ref
}
end
def point_projection_id
netex_identifier.change(type: 'PointProjection').to_s
end
def scheduled_stop_point_ref
decorate(with: ScheduledStopPoint).scheduled_stop_point_ref
end
end
# Create ScheduledStopPoint or ScheduledStopPointRef from a Chouette::StopPoint
#
# <ScheduledStopPoint id="chouette:ScheduledStopPoint:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any"/>
# <ScheduledStopPointRef ref="chouette:ScheduledStopPoint:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC"/>
class ScheduledStopPoint < Base
def scheduled_stop_point
Netex::ScheduledStopPoint.new(scheduled_stop_point_attributes)
end
def scheduled_stop_point_ref
Netex::Reference.new(netex_identifier.to_s, type: 'ScheduledStopPointRef')
end
def scheduled_stop_point_attributes
{
id: netex_identifier.to_s,
data_source_ref: data_source_ref
}
end
def netex_identifier
@netex_identifier ||= super&.change(type: 'ScheduledStopPoint')
end
end
# Create a PassengerStopAssignment from a Chouette::StopPoint
#
# <PassengerStopAssignment id="chouette:PassengerStopAssignment:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any" order="0">
# <ScheduledStopPointRef ref="chouette:ScheduledStopPoint:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any"/>
# <QuayRef ref="chouette:StopArea:fe90c70f-d9ac-45cd-b044-0d4adcfaa997:LOC"/>
# </PassengerStopAssignment>
class PassengerStopAssignment < Base
def netex_identifier
@netex_identifier ||= super&.change(type: 'PassengerStopAssignment')
end
def stop_area_code
code_provider.stop_areas.code(stop_area_id)
end
def stop_area_area_type
__getobj__.try(:stop_area_area_type) || stop_area&.area_type
end
def netex_quay?
stop_area_area_type&.to_sym == Chouette::AreaType::QUAY
end
def passenger_stop_assignment
Netex::PassengerStopAssignment.new(passenger_stop_assignment_attributes).tap do |passenger_stop_assignment|
if netex_quay?
passenger_stop_assignment.quay_ref = quay_ref
else
passenger_stop_assignment.stop_place_ref = stop_place_ref
end
end
end
def passenger_stop_assignment_attributes
{
id: netex_identifier.to_s,
data_source_ref: data_source_ref,
order: 0,
scheduled_stop_point_ref: scheduled_stop_point_ref
}
end
def quay_ref
Netex::Reference.new(stop_area_code, type: 'QuayRef')
end
def stop_place_ref
Netex::Reference.new(stop_area_code, type: 'StopPlaceRef')
end
def scheduled_stop_point_ref
decorate(with: ScheduledStopPoint).scheduled_stop_point_ref
end
end
# Create a StopPointInJourneyPattern from a Chouette::StopPoint and a JourneyPattern identifier
# <ServiceJourneyPattern ...>
# <pointsInSequence>
# <StopPointInJourneyPattern id="chouette:StopPointInJourneyPattern:6969284b-5a42-46a5-b5e1-ae3d2d35bd23-2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any" order="1">
# <ScheduledStopPointRef ref="chouette:ScheduledStopPoint:2f924949-e287-4f10-bbe0-4a50f9debf9e:LOC" version="any"/>
# <ForAlighting>true</ForAlighting>
# <ForBoarding>true</ForBoarding>
# </StopPointInJourneyPattern>
# <StopPointInJourneyPattern id="chouette:StopPointInJourneyPattern:6969284b-5a42-46a5-b5e1-ae3d2d35bd23-95c5bfc9-62f9-4490-9a57-3fa2fd6c3013:LOC" version="any" order="2">
# <ScheduledStopPointRef ref="chouette:ScheduledStopPoint:95c5bfc9-62f9-4490-9a57-3fa2fd6c3013:LOC" version="any"/>
# <ForAlighting>true</ForAlighting>
# <ForBoarding>true</ForBoarding>
# </StopPointInJourneyPattern>
# </pointsInSequence>
# </ServiceJourneyPattern>
class StopPointInJourneyPattern < Base
def initialize(stop_point, journey_pattern_id:, **attributes)
super stop_point, attributes
@journey_pattern_id = journey_pattern_id
end
attr_reader :journey_pattern_id
def stop_point_in_journey_pattern
Netex::StopPointInJourneyPattern.new stop_point_in_journey_pattern_attributes
end
def stop_point_in_journey_pattern_ref
Netex::Reference.new(netex_identifier.to_s, type: Netex::StopPointInJourneyPattern)
end
def stop_point_in_journey_pattern_attributes
{
id: netex_identifier.to_s,
order: netex_order,
scheduled_stop_point_ref: scheduled_stop_point_ref,
for_boarding: netex_for_boarding,
for_alighting: netex_for_alighting
}
end
def netex_identifier
@netex_identifier ||= super.merge(Code::Value.parse(journey_pattern_code), type: "StopPointInJourneyPattern")
end
def journey_pattern_code
code_provider.journey_patterns.code(journey_pattern_id)
end
def netex_for_boarding
for_boarding == "normal"
end
def netex_for_alighting
for_alighting == "normal"
end
def scheduled_stop_point_ref
decorate(with: ScheduledStopPoint).scheduled_stop_point_ref
end
end
# Creates a 'pseudo' StopPoint with only id attribute
# Allows to use (some) Decorators from a stop_point_id
#
# pseudo_stop_point = StopPointDecorator.pseudo_stop_point(id: stop_point_id)
# decorate(pseudo_stop_point, journey_pattern_id: journey_pattern_id, with: StopPointDecorator::StopPointInJourneyPattern)
#
def self.pseudo_stop_point(**attributes)
PseudoStopPoint.new(**attributes)
end
class PseudoStopPoint
def initialize(id:)
@id = id
end
attr_reader :id
end
end
class Routes < Part
delegate :routes, to: :export_scope
def export_part
routes.includes(:line, :stop_points, :codes).find_each do |route|
tags = resource_tagger.tags_for(route.line_id)
tagged_target = TaggedTarget.new(target, tags)
decorated_route = decorate(route)
# Export Direction before the Route to detect local reference
tagged_target << decorated_route.direction if decorated_route.direction
tagged_target << decorated_route.netex_resource
decorated_route.routing_constraint_zones.each do |zone|
tagged_target << zone
end
end
end
class Decorator < ModelDecorator
delegate :line_routing_constraint_zones, to: :line
def netex_attributes
super.merge(
data_source_ref: data_source_ref,
name: netex_name,
line_ref: line_ref,
direction_type: direction_type,
points_in_sequence: points_in_sequence,
key_list: netex_alternate_identifiers,
direction_ref: direction_ref
)
end
def netex_resource
Netex::Route.new netex_attributes
end
def netex_name
published_name.presence || name
end
def direction_id
netex_identifier.change(type: 'Direction').to_s
end
def direction
@direction ||= Netex::Direction.new(
id: direction_id,
data_source_ref: data_source_ref,
name: published_name
) if published_name
end
def direction_ref
Netex::Reference.new(direction_id, type: 'DirectionRef') if direction
end
def direction_type
wayback.to_s
end
def line_ref
if line_code = code_provider.lines.code(line_id)
Netex::Reference.new(line_code, type: Netex::Line)
end
end
def points_in_sequence
decorated_point_on_routes.map(&:point_on_route)
end
def decorated_point_on_routes
@decorated_stop_points ||= stop_points.map do |stop_point|
decorate(stop_point, data_source_ref: data_source_ref, with: StopPointDecorator::PointOnRoute)
end
end
def stop_area_ids
@stop_area_ids ||= stop_points.map(&:stop_area_id)
end
def filter_line_routing_constraint_zones
line_routing_constraint_zones.select do |line_routing_constraint_zone|
(line_routing_constraint_zone.stop_area_ids & stop_area_ids).many?
end
end
def routing_constraint_zones
filter_line_routing_constraint_zones.map do |line_routing_constraint_zone|
LineRoutingConstraintZoneDecorator.new(line_routing_constraint_zone, route: self, code_provider: code_provider).netex_resource
end
end
class LineRoutingConstraintZoneDecorator < ModelDecorator
delegate :line, to: :route
attr_accessor :route
def netex_attributes
{
id: merged_id,
name: name,
members: scheduled_stop_point_refs,
lines: line_refs,
zone_use: zone_use
}
end
def merged_id
Code::Value.merge(code_provider.code(route), id, type: "RoutingConstraintZone")
end
def zone_use
"cannotBoardAndAlightInSameZone"
end
def line_refs
if line_code = code_provider.lines.code(route.line_id)
[Netex::Reference.new(line_code, type: 'LineRef')]
end
end
def stop_points
route.stop_points.select { |stop_point| stop_area_ids.include? stop_point.stop_area_id }
end
def scheduled_stop_point_refs
stop_points.map do |stop_point|
decorate(stop_point, with: StopPointDecorator::ScheduledStopPoint).scheduled_stop_point_ref
end
end
def netex_resource
Netex::RoutingConstraintZone.new netex_attributes
end
end
end
end
class StopPoints < Part
delegate :stop_points, to: :export_scope
def export_part
stop_points.joins(:route, :stop_area).select(selected).find_each_light do |stop_point|
tags = resource_tagger.tags_for(stop_point.line_id)
tagged_target = TaggedTarget.new(target, tags)
tagged_target <<
decorate(stop_point, with: StopPointDecorator::ScheduledStopPoint).scheduled_stop_point
tagged_target <<
decorate(stop_point, with: StopPointDecorator::PassengerStopAssignment).passenger_stop_assignment
tagged_target <<
decorate(stop_point, with: StopPointDecorator::RoutePoint).route_point
end
end
private
def selected
[
'stop_points.*',
'stop_areas.area_type AS stop_area_area_type',
'routes.line_id AS line_id',
'routes.data_source_ref AS data_source_ref',
]
end
end
class RoutingConstraintZones < Part
delegate :routing_constraint_zones, to: :export_scope
def export_part
routing_constraint_zones.includes(:route).find_each do |routing_constraint_zone|
tags = resource_tagger.tags_for(routing_constraint_zone.route.line_id)
tagged_target = TaggedTarget.new(target, tags)
decorated_zone = decorate(routing_constraint_zone)
tagged_target << decorated_zone.netex_resource
end
end
class Decorator < ModelDecorator
def netex_attributes
super.merge(
data_source_ref: data_source_ref,
name: name,
members: scheduled_stop_point_refs,
lines: line_refs,
zone_use: zone_use
)
end
def scheduled_stop_point_refs
stop_points.map do |stop_point|
decorate(stop_point, with: StopPointDecorator::ScheduledStopPoint).scheduled_stop_point_ref
end
end
def line_refs
if line_code = code_provider.lines.code(route.line_id)
[ Netex::Reference.new(line_code, type: 'LineRef') ]
end
end
def netex_resource
Netex::RoutingConstraintZone.new netex_attributes
end
def zone_use
"cannotBoardAndAlightInSameZone"
end
end
end
class JourneyPatterns < Part
delegate :journey_patterns, to: :export_scope
def export_part
journey_patterns.includes(:route, :codes, :stop_points).find_each do |journey_pattern|
tags = resource_tagger.tags_for(journey_pattern.route.line_id)
tagged_target = TaggedTarget.new(target, tags)
decorated_journey_pattern = decorate(journey_pattern)
# Export Destination Displays before the JourneyPattern to detect local reference
tagged_target << decorated_journey_pattern.destination_display if journey_pattern.published_name.present?
tagged_target << decorated_journey_pattern.netex_resource
end
end
class Decorator < ModelDecorator
def netex_attributes
super.merge(
data_source_ref: data_source_ref,
name: name,
route_ref: route_ref,
points_in_sequence: points_in_sequence,
key_list: netex_alternate_identifiers,
destination_display_ref: destination_display_ref
)
end
def netex_resource
Netex::ServiceJourneyPattern.new netex_attributes
end
def route_ref
Netex::Reference.new(code_provider.routes.code(route_id), type: 'RouteRef')
end
def destination_display_id
netex_identifier.change(type: 'DestinationDisplay').to_s
end
def destination_display
@destination_display ||= Netex::DestinationDisplay.new(
id: destination_display_id,
data_source_ref: data_source_ref,
front_text: published_name
) if published_name.present?
end
def destination_display_ref
Netex::Reference.new(destination_display_id, type: Netex::DestinationDisplay) if published_name.present?
end
def points_in_sequence
decorated_stop_points.map(&:stop_point_in_journey_pattern)
end
def decorated_stop_points
@decorated_stop_points ||= stop_points.map do |stop_point|
decorate(stop_point, journey_pattern_id: id, with: StopPointDecorator::StopPointInJourneyPattern)
end
end
end
end
class VehicleJourneyAtStops < Part
def export_part
stop_point_in_journey_pattern_ref_cache = {}
passing_time_cache = {}
vehicle_journey_at_stops.find_each_light do |light_vehicle_journey_at_stop|
decorated_vehicle_journey_at_stop = decorate(
light_vehicle_journey_at_stop,
stop_point_in_journey_pattern_ref_cache: stop_point_in_journey_pattern_ref_cache,
passing_time_cache: passing_time_cache
)
target << decorated_vehicle_journey_at_stop.netex_resource
end
end
def vehicle_journey_at_stops
export_scope.vehicle_journey_at_stops
.joins(stop_point: :stop_area, vehicle_journey: { journey_pattern: :route })
.order(:vehicle_journey_id, "stop_points.position": :asc)
.select(
"vehicle_journey_at_stops.*",
"vehicle_journeys.journey_pattern_id AS journey_pattern_id",
"vehicle_journeys.id AS vehicle_journey_id",
"stop_areas.time_zone AS time_zone",
)
end
class Decorator < ModelDecorator
def netex_attributes
{
stop_point_in_journey_pattern_ref: stop_point_in_journey_pattern_ref
}.tap do |attributes|
if departure_passing_time
attributes[:departure_time] = departure_passing_time.netex_time
attributes[:departure_day_offset] = departure_passing_time.netex_day_offset
end
if arrival_passing_time
attributes[:arrival_time] = arrival_passing_time.netex_time
attributes[:arrival_day_offset] = arrival_passing_time.netex_day_offset
end
end
end
def arrival_passing_time
@arrival_passing_time ||= passing_time(time: arrival_time, day_offset: arrival_day_offset, time_zone: time_zone)
end
def departure_passing_time
@departure_passing_time ||= passing_time(time: departure_time, day_offset: departure_day_offset, time_zone: time_zone)
end
def netex_resource
Netex::TimetabledPassingTime.new(netex_attributes).with_tag(parent_id: parent_code)
end
def parent_code
code_provider.vehicle_journeys.code(vehicle_journey_id)
end
def journey_pattern_id
__getobj__.try(:journey_pattern_id) || try(:journey_pattern)&.id
end
def stop_point_in_journey_pattern_ref
return nil unless journey_pattern_id
stop_point_in_journey_pattern_ref_cache[[stop_point_id, journey_pattern_id]] ||= decorated_stop_point.stop_point_in_journey_pattern_ref
end
def decorated_stop_point
decorate(pseudo_stop_point, journey_pattern_id: journey_pattern_id, with: StopPointDecorator::StopPointInJourneyPattern)
end
def pseudo_stop_point
StopPointDecorator.pseudo_stop_point(id: stop_point_id)
end
attr_writer :stop_point_in_journey_pattern_ref_cache
def stop_point_in_journey_pattern_ref_cache
@stop_point_in_journey_pattern_ref_cache ||= {}
end
attr_writer :passing_time_cache
def passing_time_cache
@passing_time_cache ||= {}
end
def passing_time(time:, day_offset:, time_zone:)
return nil if time.blank?
passing_time_cache[[time, day_offset, time_zone]] ||= PassingTime.new(time: time, day_offset: day_offset, time_zone: time_zone)
end
end
class PassingTime
def initialize(time:, day_offset:, time_zone:)
@time, @day_offset, @time_zone = time, day_offset, time_zone
# For performance purpose, everything is computed once
@local_time_of_day = TimeOfDay.parse(@time, day_offset: @day_offset, time_zone: @time_zone)
@local_day_offset = @local_time_of_day.day_offset
@netex_time = Netex::Time.new local_time_of_day.hour, local_time_of_day.minute, local_time_of_day.second
freeze
end
attr_reader :time, :day_offset, :time_zone, :local_time_of_day, :local_day_offset, :netex_time
alias netex_day_offset local_day_offset
end
end
class VehicleJourneys < Part
def export_part
# For the moment, no CustomField is used in VehicleJourney. See CHOUETTE-3939
Chouette::VehicleJourney.without_custom_fields do
vehicle_journeys.each_instance do |vehicle_journey|
tags = resource_tagger.tags_for(vehicle_journey.line_id)
tagged_target = TaggedTarget.new(target, tags)
decorated_vehicle_journey = decorate(vehicle_journey, code_space_keys: code_space_keys)
tagged_target << decorated_vehicle_journey.netex_resource
end
end
end
def vehicle_journeys
Query.new(export_scope.vehicle_journeys).scope
end
# TODO: integrate this use case in AlternateIdentifiersExtractor
def code_space_keys
@code_space_keys ||= workgroup.code_spaces.pluck(:id, :short_name).to_h
end
class Query
def initialize(vehicle_journeys)
@vehicle_journeys = vehicle_journeys
end
attr_accessor :vehicle_journeys
def scope
scope = vehicle_journeys.joins(:route).select(selected)
scope = scope.left_joins(:codes).select(vehicle_journey_codes).group(group_by)
scope.joins(:vehicle_journey_time_table_relationships).select(time_table_ids)
end
private
def selected
<<~SQL
vehicle_journeys.*,
routes.line_id AS line_id
SQL
end
def time_table_ids
<<~SQL
array_agg(DISTINCT time_tables_vehicle_journeys.time_table_id) AS timetable_ids
SQL
end
def vehicle_journey_codes
<<~SQL
array_agg(
DISTINCT
jsonb_build_object(
'id', referential_codes.code_space_id,
'value', referential_codes.value
)
) AS vehicle_journey_codes
SQL
end
def group_by
<<~SQL
vehicle_journeys.id,
routes.line_id
SQL
end
end
class Decorator < ModelDecorator
attr_accessor :code_space_keys
def netex_attributes
super.merge(
data_source_ref: data_source_ref,
name: published_journey_name,
journey_pattern_ref: journey_pattern_ref,
public_code: published_journey_identifier,
day_types: day_types,
key_list: netex_alternate_identifiers
)
end
def netex_resource
Netex::ServiceJourney.new netex_attributes
end
def code_space_key(code_space_id)
code_space_keys[code_space_id]
end
def netex_alternate_identifiers
return if code_space_keys.blank? || try(:vehicle_journey_codes).blank?
vehicle_journey_codes.map do |vehicle_journey_code|
Netex::KeyValue.new(
key: code_space_key(vehicle_journey_code['id'].to_i),
value: vehicle_journey_code['value'],
type_of_key: 'ALTERNATE_IDENTIFIER'
)
end
end
def journey_pattern_ref
Netex::Reference.new(journey_pattern_code, type: 'JourneyPatternRef')
end
def journey_pattern_code
code_provider.journey_patterns.code(journey_pattern_id)
end
def timetable_codes
if identifiers = try(:timetable_ids)
code_provider.time_tables.codes(identifiers)
else
code_provider.codes(time_tables)
end
end
def day_types
timetable_codes.map do |code|
Netex::Reference.new(code, type: 'DayTypeRef')
end
end
end
end
class VehicleJourneyStopAssignments < Part
def export_part
vehicle_journey_at_stops.find_each do |vehicle_journey_at_stop|
tags = resource_tagger.tags_for(vehicle_journey_at_stop.line_id)
tagged_target = TaggedTarget.new(target, tags)
netex_resource = Decorator.new(vehicle_journey_at_stop).netex_resource
tagged_target << netex_resource
end
end
def vehicle_journey_at_stops
export_scope.vehicle_journey_at_stops.where.not(stop_area: nil)
.joins(:stop_point, :stop_area, vehicle_journey: :route)
.select(*selected_columns)
end
def selected_columns
['vehicle_journey_at_stops.*',
'vehicle_journeys.objectid AS vehicle_journey_objectid',
"COALESCE(vehicle_journeys.data_source_ref, 'none') AS vehicle_journey_data_source_ref",
'stop_points.objectid AS stop_point_objectid',
'stop_areas.objectid AS stop_area_objectid',
'stop_points.position AS stop_point_position',
'routes.line_id as line_id'
]
end
class Decorator < SimpleDelegator
def netex_attributes
{
id: objectid,
data_source_ref: vehicle_journey_data_source_ref,
scheduled_stop_point_ref: scheduled_stop_point_ref,
stop_place_ref: stop_place_ref,
quay_ref: quay_ref,
vehicle_journey_refs: vehicle_journey_refs
}
end
def netex_resource
Netex::VehicleJourneyStopAssignment.new(netex_attributes)
end
def objectid
Code::Value.merge(vehicle_journey_objectid, stop_point_position, type: 'VehicleJourneyStopAssignment').to_s
end
def stop_point_position
__getobj__.try(:stop_point_position) || stop_point&.position
end
def stop_point_objectid
__getobj__.try(:stop_point_objectid) || stop_point&.objectid
end
def stop_area_objectid
__getobj__.try(:stop_area_objectid) || stop_area&.objectid
end
def vehicle_journey_objectid
__getobj__.try(:vehicle_journey_objectid) || vehicle_journey&.objectid
end
def vehicle_journey_data_source_ref
loaded_value = __getobj__.try(:vehicle_journey_data_source_ref)
return nil if loaded_value == 'none'
loaded_value || vehicle_journey&.data_source_ref
end
def scheduled_stop_point_ref
Netex::Reference.new(stop_point_objectid, type: 'ScheduledStopPointRef')
end
def stop_place_ref
Netex::Reference.new(stop_area_objectid, type: 'StopPlaceRef')
end
def quay_ref
Netex::Reference.new(stop_area_objectid, type: 'QuayRef')
end
def vehicle_journey_refs
[Netex::Reference.new(vehicle_journey_objectid, type: 'ServiceJourney')]
end
end
end
class PeriodDecorator < SimpleDelegator
attr_accessor :day_type_ref, :time_table, :code_provider
def initialize(period, day_type_ref, code_provider = nil)
super period
@day_type_ref = day_type_ref
@time_table = period.time_table
@code_provider = code_provider
end
def netex_identifier
@netex_identifier ||= begin
code_time_table = code_provider.time_tables.code(time_table.id) if code_provider
Code::Value.parse(code_time_table) if code_time_table
end
end
def operating_period
Netex::UicOperatingPeriod.new operating_period_attributes
end
def operating_period_id
netex_identifier&.merge(id, type: 'OperatingPeriod').to_s
end
def operating_period_attributes
{
id: operating_period_id,
data_source_ref: time_table.data_source_ref,
from_date: period_start,
to_date: period_end,
valid_day_bits: valid_day_bits
}
end
def valid_day_bits
to_days_bit.bitset.to_s
end
def day_type_assignment_id
netex_identifier&.merge("p#{id}", type: 'DayTypeAssignment').to_s
end
def day_type_assignment
Netex::DayTypeAssignment.new day_type_assignment_attributes
end
def day_type_assignment_attributes
{
id: day_type_assignment_id,
data_source_ref: time_table.data_source_ref,
operating_period_ref: operating_period_ref,
day_type_ref: day_type_ref,
order: 0
}
end
def operating_period_ref
Netex::Reference.new(operating_period_id, type: 'OperatingPeriodRef')
end
end
class DateDecorator < SimpleDelegator
attr_accessor :day_type_ref, :time_table, :code_provider
def initialize(date, day_type_ref, code_provider = nil)
super date
@day_type_ref = day_type_ref
@time_table = date.time_table
@code_provider = code_provider
end
def netex_identifier
@netex_identifier ||= begin
code_time_table = code_provider.time_tables.code(time_table.id) if code_provider
Code::Value.parse(code_time_table) if code_time_table
end
end
def day_type_assignment
Netex::DayTypeAssignment.new day_type_assignment_attributes
end
def date_type_assignment_id
netex_identifier&.merge("d#{id}", type: 'DayTypeAssignment').to_s
end
def day_type_assignment_attributes
{
id: date_type_assignment_id,
data_source_ref: time_table.data_source_ref,
date: date,
is_available: in_out,
day_type_ref: day_type_ref,
order: 0
}
end
end
class TimeTables < Part
delegate :time_tables, to: :export_scope
delegate :validity_period, to: :export_scope
def export_part
time_tables.includes(:periods, :dates, :codes).find_each do |time_table|
decorated_time_table = decorate(time_table, validity_period: validity_period)
tags = resource_tagger.tags_for_lines(time_table_line_ids[time_table.id])
tagged_target = TaggedTarget.new(target, tags)
decorated_time_table.netex_resources.each do |resource|
tagged_target << resource
end
end
end
def time_table_line_ids
@timetable_line_ids ||=
time_tables
.left_joins(:lines)
.group(:id)
.pluck(:id, 'array_agg(distinct line_id) as line_ids').to_h
end
class Decorator < ModelDecorator
attr_accessor :validity_period
def netex_resources
return [] unless day_type_assignment?
[day_type, exported_periods, exported_dates].flatten
end
def day_type_assignment?
decorated_dates.present? || decorated_periods.present?
end
def day_type
Netex::DayType.new day_type_attributes
end
def day_type_attributes
netex_attributes.merge(
data_source_ref: data_source_ref,
name: comment,
properties: properties,
key_list: netex_alternate_identifiers
)
end
def day_type_ref
@day_type_ref ||= Netex::Reference.new(netex_identifier.to_s, type: 'DayTypeRef')
end
def properties
[Netex::PropertyOfDay.new(days_of_week: days_of_week)]
end
DAYS = %w{monday tuesday wednesday thursday friday saturday sunday}
def days_of_week
DAYS.map { |day| day.capitalize if send(day) }.compact.join(' ')
end
def exported_periods
decorated_periods.map(&:operating_period) + decorated_periods.map(&:day_type_assignment)
end
def candidate_periods
@candidate_periods ||= validity_period ? periods.select { |period| period.intersect?(validity_period) } : periods
end
def decorated_periods
@decorated_periods ||= candidate_periods.map do |period|
PeriodDecorator.new(period, day_type_ref, code_provider)
end
end
def candidate_excluded_dates
dates.select(&:excluded?).select do |date|
candidate_periods.any? { |period| period.include? date.date }
end
end
def candidate_included_dates
included_dates = dates.select(&:included?)
return included_dates unless validity_period
included_dates.select { |date| validity_period.include? date.date }
end
def candidate_dates
candidate_excluded_dates + candidate_included_dates
end
def exported_dates
decorated_dates.map(&:day_type_assignment)
end
def decorated_dates
@decorated_dates ||= candidate_dates.map do |date|
DateDecorator.new(date, day_type_ref, code_provider)
end
end
end
end
class Organisations < Part
delegate :organisations, to: :export_scope
def export_part
organisations.find_each do |o|
target << Decorator.new(o).netex_resource
end
end
class Decorator < SimpleDelegator
def netex_resource
Netex::GeneralOrganisation.new(id: code, name: name)
end
end
end
end