1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
module ActiveRecord::Associations::Builder # :nodoc:
class HasAndBelongsToMany # :nodoc:
class JoinTableResolver # :nodoc:
KnownTable = Struct.new :join_table
class KnownClass # :nodoc:
def initialize(lhs_class, rhs_class_name)
@lhs_class = lhs_class
@rhs_class_name = rhs_class_name
@join_table = nil
end
def join_table
@join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
end
private
def klass
@lhs_class.send(:compute_type, @rhs_class_name)
end
end
def self.build(lhs_class, name, options)
if options[:join_table]
KnownTable.new options[:join_table].to_s
else
class_name = options.fetch(:class_name) {
name.to_s.camelize.singularize
}
KnownClass.new lhs_class, class_name.to_s
end
end
end
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
habtm = JoinTableResolver.build lhs_model, association_name, options
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_resolver.join_table
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 = habtm
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("_".freeze).gsub("::".freeze, "_".freeze).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 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
|