<commit_before>require 'active_record'

module ChronoModel
  module Patches

    module AsOfTimeHolder
      # Sets the virtual 'as_of_time' attribute to the given time, converting to UTC.
      #
      # See ChronoModel::Patches::AsOfTimeHolder
      #
      def as_of_time!(time)
        @_as_of_time = time.utc

        self
      end

      # Reads the virtual 'as_of_time' attribute
      #
      # See ChronoModel::Patches::AsOfTimeHolder
      #
      def as_of_time
        @_as_of_time
      end
    end

    module Relation
      include AsOfTimeHolder

      def load
        return super unless @_as_of_time && !loaded?

        super.each {|record| record.as_of_time!(@_as_of_time) }
      end

      def merge(*)
        return super unless @_as_of_time

        super.as_of_time!(@_as_of_time)
      end

      def build_arel
        return super unless @_as_of_time

        super.tap do |arel|

          arel.join_sources.each do |join|
            model = TimeMachine.chrono_models[join.left.table_name]
            next unless model

            join.left = Arel::Nodes::SqlLiteral.new(
              model.history.virtual_table_at(@_as_of_time,
                join.left.table_alias || join.left.table_name)
            )
          end

        end
      end

      def build_preloader
        ActiveRecord::Associations::Preloader.new(as_of_time: as_of_time)
      end
    end

    # Patches ActiveRecord::Associations::Preloader to add support for
    # temporal associations. This is tying itself to Rails internals
    # and it is ugly :-(.
    #
    module Preloader
      attr_reader :options

      NULL_RELATION = ActiveRecord::Associations::Preloader::NULL_RELATION
      AS_OF_PRELOAD_SCOPE = Struct.new(:as_of_time, *NULL_RELATION.members)

      def initialize(options = {})
        @options = options.freeze
      end

      def preload(records, associations, given_preload_scope = nil)
        if (as_of_time = options[:as_of_time])
          preload_scope = AS_OF_PRELOAD_SCOPE.new

          preload_scope.as_of_time = as_of_time
          given_preload_scope ||= NULL_RELATION

          NULL_RELATION.members.each do |member|
            preload_scope[member] = given_preload_scope[member]
          end
        end

        super records, associations, preload_scope
      end

      module Association
        def build_scope
          scope = super

          if preload_scope.respond_to?(:as_of_time)
            scope = scope.as_of(preload_scope.as_of_time)
          end

          return scope
        end
      end
    end

    # Patches ActiveRecord::Associations::Association to add support for
    # temporal associations.
    #
    # Each record fetched from the +as_of+ scope on the owner class will have
    # an additional "as_of_time" field yielding the UTC time of the request,<commit_msg>Add support for :through associations<commit_after>require 'active_record'

module ChronoModel
  module Patches

    module AsOfTimeHolder
      # Sets the virtual 'as_of_time' attribute to the given time, converting to UTC.
      #
      # See ChronoModel::Patches::AsOfTimeHolder
      #
      def as_of_time!(time)
        @_as_of_time = time.utc

        self
      end

      # Reads the virtual 'as_of_time' attribute
      #
      # See ChronoModel::Patches::AsOfTimeHolder
      #
      def as_of_time
        @_as_of_time
      end
    end

    module Relation
      include AsOfTimeHolder

      def load
        return super unless @_as_of_time && !loaded?

        super.each {|record| record.as_of_time!(@_as_of_time) }
      end

      def merge(*)
        return super unless @_as_of_time

        super.as_of_time!(@_as_of_time)
      end

      def build_arel
        return super unless @_as_of_time

        super.tap do |arel|

          arel.join_sources.each do |join|
            model = TimeMachine.chrono_models[join.left.table_name]
            next unless model

            join.left = Arel::Nodes::SqlLiteral.new(
              model.history.virtual_table_at(@_as_of_time,
                join.left.table_alias || join.left.table_name)
            )
          end

        end
      end

      def build_preloader
        ActiveRecord::Associations::Preloader.new(as_of_time: as_of_time)
      end
    end

    # Patches ActiveRecord::Associations::Preloader to add support for
    # temporal associations. This is tying itself to Rails internals
    # and it is ugly :-(.
    #
    module Preloader
      attr_reader :options

      NULL_RELATION = ActiveRecord::Associations::Preloader::NULL_RELATION
      AS_OF_PRELOAD_SCOPE = Struct.new(:as_of_time, *NULL_RELATION.members)

      def initialize(options = {})
        @options = options.freeze
      end

      def preload(records, associations, given_preload_scope = nil)
        if (as_of_time = options[:as_of_time])
          preload_scope = if given_preload_scope.respond_to?(:as_of_time!)
            given_preload_scope.as_of_time!(as_of_time)
          else
            null_relation_preload_scope(as_of_time, given_preload_scope)
          end
        end

        super records, associations, preload_scope
      end

      private
        def null_relation_preload_scope(as_of_time, given_preload_scope)
          preload_scope = AS_OF_PRELOAD_SCOPE.new

          preload_scope.as_of_time = as_of_time
          given_preload_scope ||= NULL_RELATION

          NULL_RELATION.members.each do |member|
            preload_scope[member] = given_preload_scope[member]
          end

          return preload_scope
        end

      module Association
        def build_scope
          scope = super

          if preload_scope.respond_to?(:as_of_time)
            scope = scope.as_of(preload_scope.as_of_time)
          end

          return scope
        end
      end
    end

    # Patches ActiveRecord::Associations::Association to add support for
    # temporal associations.
    #
    # Each record fetched from the +as_of+ scope on the owner class will have
    # an additional "as_of_time" field yielding the UTC time of the request,
{'fraction_non_alphanumeric': 0.06645829906234578, 'fraction_numerical': 0.0, 'mean_word_length': 2.2220455749867516, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '939819', 'n_tokens_mistral': 1869, 'n_tokens_neox': 1808, 'n_words': 420}