af83/chouette-core

View on GitHub
app/inserters/id_map_inserter.rb

Summary

Maintainability
A
0 mins
Test Coverage
class IdMapInserter < ByClassInserter

  attr_reader :target
  def initialize(target, options = {})
    @target = target
    options.each { |k,v| send "#{k}=", v }
  end

  attr_accessor :strict
  alias strict? strict

  def new_primary_key!(model_class, old_primary_key)
    new_primary_key = self.for(model_class).new_primary_key(old_primary_key)

    if new_primary_key.nil? && strict?
      raise "No new primary key for #{model_class.name}:#{old_primary_key}"
    end

    new_primary_key
  end

  def new_vehicle_journey_primary_key!(old_primary_key)
    @vehicle_journey_inserter ||= self.for(Chouette::VehicleJourney)
    @vehicle_journey_inserter.new_primary_key(old_primary_key)
  end

  def new_stop_point_primary_key!(old_primary_key)
    @stop_point_inserter ||= self.for(Chouette::StopPoint)
    @stop_point_inserter.new_primary_key(old_primary_key)
  end

  def new_time_table_primary_key!(old_primary_key)
    @time_table_inserter ||= self.for(Chouette::TimeTable)
    @time_table_inserter.new_primary_key(old_primary_key)
  end

  # Reserved to test
  def register_primary_key!(model_class, old_primary_key, new_primary_key)
    self.for(model_class).register_primary_key old_primary_key, new_primary_key
  end

  def self.mapped_model_class?(model_class)
    # TODO Creates a nice method in model class :)
    ! Apartment.excluded_models.include?(model_class.name)
  end

  class Base

    attr_reader :model_class, :parent_inserter

    def initialize(model_class, parent_inserter)
      @model_class = model_class
      @parent_inserter = parent_inserter

      @current_primary_key = load_current_primary_key
      @new_primary_keys ||= Hash.new
    end

    def insert(model, options = {})
      update_relations model unless options[:skip_id_map_update_relations]

      update_primary_key model
    end

    def new_primary_key(old_primary_key)
      @new_primary_keys[old_primary_key]
    end

    def register_primary_key(old_primary_key, new_primary_key)
      if @new_primary_keys.has_key?(old_primary_key)
        raise "A model with primary key #{old_primary_key} already inserted"
      end
      @new_primary_keys[old_primary_key] = new_primary_key
    end

    def has_primary_key?
      ! model_class.column_for_attribute(:id).null
    end

    def update_primary_key(model)
      return unless has_primary_key?
      current_primary_key = model.id

      unless current_primary_key
        raise "No existing primary key for #{model.inspect}"
      end

      new_primary_key = next_primary_key
      register_primary_key current_primary_key, new_primary_key
      model.id = new_primary_key
    end

    def update_relations(model)
      relation_updaters.each do |updater|
        updater.update model
      end
    end

    def relation_updaters
      @relation_updaters ||= create_relation_updaters_on_reflection
    end

    def create_relation_updaters_on_reflection
      # We need to use a set to de-duplicate belongs_to relations
      # Some models have "light" relations which duplicate some relations
      model_class.reflect_on_all_associations(:belongs_to).map do |relation|
        # Doesn't support polymorphic relations
        if IdMapInserter.mapped_model_class?(relation.klass)
          RelationUpdater.new relation.foreign_key, relation.klass, parent_inserter
        end
      end.compact.to_set
    end

    def next_primary_key
      @current_primary_key += 1
    end

    def load_current_primary_key
      return 0 unless has_primary_key?

      parent_inserter.target.switch do
        # When using a cursor, the cursor scope is present
        model_class.unscope(:where).maximum(:id) || 0
      end
    end

    def flush
      @new_primary_keys.clear
    end

  end

  class TimeTable < Base

    def update_relations(timetable)
      # We want to ignore the TimeTable created_from
      timetable.created_from_id = nil
      super
    end

  end

  class VehicleJourneyAtStop < Base

    def load_current_primary_key
      parent_inserter.target.switch do |target|
        # When using a cursor, the cursor scope is present
        target.vehicle_journey_at_stops.unscope(:where).maximum(:id) || 0
      end
    end

    def update_primary_key(model)
      model.id = next_primary_key
    end

    def update_relations(vehicle_journey_at_stop)
      if (vehicle_journey_id = vehicle_journey_at_stop.vehicle_journey_id)
        vehicle_journey_at_stop.vehicle_journey_id =
          parent_inserter.new_vehicle_journey_primary_key!(vehicle_journey_id)
      end

      if (stop_point_id = vehicle_journey_at_stop.stop_point_id)
        if (new_stop_point_id = parent_inserter.new_stop_point_primary_key!(stop_point_id))
          vehicle_journey_at_stop.stop_point_id = new_stop_point_id
        end
      end
    end

  end

  class TimeTableDate < Base

    def update_primary_key(model)
      model.id = next_primary_key
    end

    def update_relations(date)
      if (time_table_id = date.time_table_id)
        date.time_table_id =
          parent_inserter.new_time_table_primary_key!(time_table_id)
      end
    end

  end

  class ReferentialCode < Base

    def update_relations(code)
      if (resource_id = code.resource_id)
        unless code.resource_type == 'Chouette::VehicleJourney'
          raise "Doesn't support other resource than VehicleJourney (for the moment)"
        end
        code.resource_id = parent_inserter.new_vehicle_journey_primary_key!(resource_id)
      end
    end

  end

  class RelationUpdater

    attr_reader :attribute, :associated_model_class, :primary_key_resolver

    def initialize(attribute, associated_model_class, primary_key_resolver)
      @attribute = attribute.to_s
      @associated_model_class = associated_model_class
      @primary_key_resolver = primary_key_resolver
    end

    def update(model)
      old_primary_key = model.send(attribute)
      return if old_primary_key.nil?

      new_primary_key = retrieve_associated_model_id!(old_primary_key)
      model.send "#{attribute}=", new_primary_key if new_primary_key
    end

    def retrieve_associated_model_id!(old_primary_key)
      primary_key_resolver.new_primary_key!(associated_model_class, old_primary_key)
    end

    def eql?(other)
      attribute == other.attribute &&
      associated_model_class == other.associated_model_class
    end

    def hash
      [ attribute, associated_model_class.name ].hash
    end

    def inspect
      "#<IdMapInserter::RelationUpdater:#{hash} @attribute=#{attribute}, @associated_model_class=#{associated_model_class.name}"
    end

  end

end