129 lines
3.5 KiB
Ruby
129 lines
3.5 KiB
Ruby
# typed: false
|
|
# frozen_string_literal: true
|
|
|
|
module HasAuxTable
|
|
class AuxTableConfig < T::Struct
|
|
extend T::Sig
|
|
|
|
const :table_name, Symbol
|
|
const :aux_association_name, Symbol
|
|
const :main_association_name, Symbol
|
|
const :main_class, T.class_of(ActiveRecord::Base)
|
|
const :model_class, T.class_of(ActiveRecord::Base)
|
|
const :foreign_key, T.any(Symbol, T::Array[Symbol])
|
|
const :primary_key, T.any(Symbol, T::Array[Symbol])
|
|
|
|
sig { void }
|
|
def load_aux_schema
|
|
model_class.load_schema
|
|
end
|
|
|
|
sig { params(main_model: ActiveRecord::Base).returns(ActiveRecord::Base) }
|
|
def ensure_aux_target(main_model)
|
|
aux_association = main_model.association(self.aux_association_name)
|
|
aux_association.target ||= aux_association.build
|
|
end
|
|
|
|
sig do
|
|
params(name: Symbol, value: T.untyped, block: T.proc.void).returns(
|
|
Arel::Nodes::Node
|
|
)
|
|
end
|
|
def aux_bind_attribute(name, value, &block)
|
|
arel_attr = model_class.arel_table[name]
|
|
aux_bind =
|
|
model_class.predicate_builder.build_bind_attribute(
|
|
arel_attr.name,
|
|
value
|
|
)
|
|
block.call(arel_attr, aux_bind)
|
|
end
|
|
|
|
sig do
|
|
params(
|
|
main_class: T.class_of(ActiveRecord::Base),
|
|
method_name: Symbol
|
|
).void
|
|
end
|
|
def define_aux_attribute_delegate(main_class, method_name)
|
|
aux_config = self
|
|
main_class.define_method(method_name) do |*args, **kwargs|
|
|
aux_model = aux_config.ensure_aux_target(self)
|
|
aux_model.public_send(method_name, *args, **kwargs)
|
|
end
|
|
end
|
|
|
|
sig do
|
|
params(
|
|
relation: T.any(ActiveRecord::Relation, T.class_of(ActiveRecord::Base)),
|
|
conditions: T::Hash[String, T.untyped]
|
|
).returns(ActiveRecord::Relation)
|
|
end
|
|
def apply_split_conditions!(relation, conditions)
|
|
main_conditions, aux_conditions =
|
|
self.partition_by_aux_columns(conditions)
|
|
relation = relation.where(main_conditions) if main_conditions.any?
|
|
if aux_conditions.any?
|
|
relation = relation.where(aux_association_name => aux_conditions)
|
|
end
|
|
relation
|
|
end
|
|
|
|
sig do
|
|
params(conditions: T::Hash[String, T.untyped]).returns(
|
|
T::Hash[String, T.untyped]
|
|
)
|
|
end
|
|
def remap_conditions(conditions)
|
|
main, aux = partition_by_aux_columns(conditions)
|
|
main.merge!(aux_association_name => aux) if aux.any?
|
|
main
|
|
end
|
|
|
|
sig do
|
|
params(
|
|
main_model: ActiveRecord::Base,
|
|
aux_args: T::Hash[Symbol, T.untyped]
|
|
).void
|
|
end
|
|
def assign_aux_attributes(main_model, aux_args)
|
|
aux_model = self.ensure_aux_target(main_model)
|
|
aux_model.assign_attributes(aux_args)
|
|
end
|
|
|
|
sig { returns(T::Array[String]) }
|
|
def aux_column_names
|
|
@aux_column_names ||=
|
|
begin
|
|
rejected_columns = [
|
|
self.foreign_key,
|
|
self.primary_key,
|
|
"created_at",
|
|
"updated_at"
|
|
].flatten.map(&:to_s)
|
|
|
|
model_class
|
|
.column_names
|
|
.reject { |col| rejected_columns.include?(col.to_s) }
|
|
.map(&:to_s)
|
|
end
|
|
end
|
|
|
|
sig { params(name: T.any(Symbol, String)).returns(T::Boolean) }
|
|
def is_aux_column?(name)
|
|
aux_column_names.include?(name.to_s)
|
|
end
|
|
|
|
private
|
|
|
|
sig do
|
|
params(hash: T::Hash[String, T.untyped]).returns(
|
|
[T::Hash[String, T.untyped], T::Hash[String, T.untyped]]
|
|
)
|
|
end
|
|
def partition_by_aux_columns(hash)
|
|
hash.partition { |k, _| !self.is_aux_column?(k) }.map(&:to_h)
|
|
end
|
|
end
|
|
end
|