# frozen_string_literal: true
module ActiveRecord::Associations::Builder # :nodoc:
class HasAndBelongsToMany # :nodoc:
attr_reader :lhs_model, :association_name, :options
def initialize(association_name, lhs_model, options)
@association_name = association_name
@lhs_model = lhs_model
@options = options
end
def through_model
join_model = Class.new(ActiveRecord::Base) {
class << self
attr_accessor :left_model
attr_accessor :name
attr_accessor :table_name_resolver
attr_accessor :left_reflection
attr_accessor :right_reflection
end
def self.table_name
# Table name needs to be resolved lazily
# because RHS class might not have been loaded
@table_name ||= table_name_resolver.call
end
def self.compute_type(class_name)
left_model.compute_type class_name
end
def self.add_left_association(name, options)
belongs_to name, required: false, **options
self.left_reflection = _reflect_on_association(name)
end
def self.add_right_association(name, options)
rhs_name = name.to_s.singularize.to_sym
belongs_to rhs_name, required: false, **options
self.right_reflection = _reflect_on_association(rhs_name)
end
def self.retrieve_connection
left_model.retrieve_connection
end
private
def self.suppress_composite_primary_key(pk)
pk unless pk.is_a?(Array)
end
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
join_model.table_name_resolver = -> { table_name }
join_model.left_model = lhs_model
join_model.add_left_association :left_side, anonymous_class: lhs_model
join_model.add_right_association association_name, belongs_to_options(options)
join_model
end
def middle_reflection(join_model)
middle_name = [lhs_model.name.downcase.pluralize,
association_name].join("_").gsub("::", "_").to_sym
middle_options = middle_options join_model
HasMany.create_reflection(lhs_model,
middle_name,
nil,
middle_options)
end
private
def middle_options(join_model)
middle_options = {}
middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
middle_options[:source] = join_model.left_reflection.name
if options.key? :foreign_key
middle_options[:foreign_key] = options[:foreign_key]
end
middle_options
end
def table_name
if options[:join_table]
options[:join_table].to_s
else
class_name = options.fetch(:class_name) {
association_name.to_s.camelize.singularize
}
klass = lhs_model.send(:compute_type, class_name.to_s)
[lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
end
end
def belongs_to_options(options)
rhs_options = {}
if options.key? :class_name
rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
rhs_options[:class_name] = options[:class_name]
end
if options.key? :association_foreign_key
rhs_options[:foreign_key] = options[:association_foreign_key]
end
rhs_options
end
end
end