# frozen_string_literal: true require_relative "./bitemporal.rb" module ActiveRecord::Bitemporal module Patches using Module.new { refine ::ActiveRecord::Reflection::AssociationReflection do # handle raise error, when `polymorphic? == true` def klass polymorphic? ? nil : super end end } using BitemporalChecker # nested_attributes 用の拡張 module Persistence using Module.new { refine Persistence do def copy_bitemporal_option(src, dst) return unless [src.class, dst.class].all? { |klass| # NOTE: Can't call refine method. # klass.bi_temporal_model? klass.include?(ActiveRecord::Bitemporal) } dst.bitemporal_option_merge! src.bitemporal_option end end } # MEMO: このメソッドは BTDM 以外にもフックする必要がある def assign_nested_attributes_for_one_to_one_association(association_name, _attributes) super target = send(association_name) return if target.nil? || !target.changed? copy_bitemporal_option(self, target) end def assign_nested_attributes_for_collection_association(association_name, _attributes_collection) # Preloading records send(association_name).load if association(association_name).klass&.bi_temporal_model? super send(association_name)&.each do |target| next unless target.changed? copy_bitemporal_option(self, target) end end end module Association def skip_statement_cache?(scope) super || bi_temporal_model? end def scope scope = super return scope unless scope.bi_temporal_model? scope_ = scope if owner.class&.bi_temporal_model? if owner.valid_datetime valid_datetime = owner.valid_datetime scope_ = scope_.valid_at(valid_datetime) scope_.merge!(scope_.bitemporal_value[:through].valid_at(valid_datetime)) if scope_.bitemporal_value[:through] end if owner.transaction_datetime transaction_datetime = owner.transaction_datetime scope_ = scope_.transaction_at(transaction_datetime) scope_.merge!(scope_.bitemporal_value[:through].transaction_at(transaction_datetime)) if scope_.bitemporal_value[:through] end end return scope_ end private def bi_temporal_model? owner.class.bi_temporal_model? && klass&.bi_temporal_model? end def matches_foreign_key?(record) return super unless owner.class.bi_temporal_model? begin original_owner_id = owner.id owner.id = owner.read_attribute(owner.class.bitemporal_id_key) super ensure owner.id = original_owner_id end end end module ThroughAssociation def target_scope scope = super return scope unless scope.bi_temporal_model? reflection.chain.drop(1).each do |reflection| klass = reflection.klass&.scope_for_association&.klass next unless klass&.bi_temporal_model? scope.bitemporal_value[:through] = klass end scope end end module AssociationReflection JoinKeys = Struct.new(:key, :foreign_key) def get_join_keys(association_klass) return super unless association_klass&.bi_temporal_model? self.belongs_to? ? JoinKeys.new(association_klass.bitemporal_id_key, join_foreign_key) : super end def primary_key(klass) return super unless klass&.bi_temporal_model? klass.bitemporal_id_key end end module Merger def merge if relation.klass.bi_temporal_model? && other.klass.bi_temporal_model? relation.bitemporal_value.merge! other.bitemporal_value end super end end module SingularAssociation # MEMO: Except for primary_key in ActiveRecord # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/associations/singular_association.rb#L34-L36 # excluding bitemporal_id_key def scope_for_create return super unless klass&.bi_temporal_model? super.except!(klass.bitemporal_id_key) end end end end