brick 1.0.19 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +14 -0
- data/lib/brick/extensions.rb +329 -66
- data/lib/brick/frameworks/rails/engine.rb +123 -109
- data/lib/brick/join_array.rb +227 -0
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +29 -5
- data/lib/generators/brick/install_generator.rb +14 -9
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82ebef3561b9cd7b8abcbbd8a30b7687485543ddf1a0900a9935dfb99299f13f
|
4
|
+
data.tar.gz: ba071d53409fd8d4833693907364c745585aeb0bb18e885fcf7cbf1bd23b9940
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69ad3ae158a8e478864db65dc808152d4b1b16bdc8779b1a651f59f199167daf00d2aa10018c5f65d5e619bbca4c09e99bc319ca736eed57023d61e6067ea365
|
7
|
+
data.tar.gz: 18ca63e176e12ea50a5168e835a2eddfa66c8f522943d42e6d895d2f3e05d0cf6d396ab28d79f98898c91670b618cc27dbad5fa0bc9129a3edcfc3f74f5db029
|
data/lib/brick/config.rb
CHANGED
@@ -74,6 +74,20 @@ module Brick
|
|
74
74
|
@mutex.synchronize { @exclude_hms = skips }
|
75
75
|
end
|
76
76
|
|
77
|
+
# Skip showing counts for these specific has_many associations when building auto-generated #index views
|
78
|
+
def skip_index_hms
|
79
|
+
@mutex.synchronize { @skip_index_hms || {} }
|
80
|
+
end
|
81
|
+
|
82
|
+
def skip_index_hms=(skips)
|
83
|
+
@mutex.synchronize do
|
84
|
+
@skip_index_hms ||= skips.each_with_object({}) do |v, s|
|
85
|
+
class_name, assoc_name = v.split('.')
|
86
|
+
(s[class_name] ||= {})[assoc_name.to_sym] = nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
77
91
|
# Associations to treat as a has_one
|
78
92
|
def has_ones
|
79
93
|
@mutex.synchronize { @has_ones }
|
data/lib/brick/extensions.rb
CHANGED
@@ -32,32 +32,101 @@
|
|
32
32
|
|
33
33
|
# Currently quadrupling up routes
|
34
34
|
|
35
|
+
|
36
|
+
# From the North app:
|
37
|
+
# undefined method `built_in_role_path' when referencing show on a subclassed STI:
|
38
|
+
# http://localhost:3000/roles/3?_brick_schema=cust1
|
39
|
+
|
40
|
+
|
35
41
|
# ==========================================================
|
36
42
|
# Dynamically create model or controller classes when needed
|
37
43
|
# ==========================================================
|
38
44
|
|
39
45
|
# By default all models indicate that they are not views
|
46
|
+
module Arel
|
47
|
+
class Table
|
48
|
+
def _arel_table_type
|
49
|
+
# AR < 4.2 doesn't have type_caster at all, so rely on an instance variable getting set
|
50
|
+
# AR 4.2 - 5.1 have buggy type_caster entries for the root node
|
51
|
+
instance_variable_get(:@_arel_table_type) ||
|
52
|
+
# 5.2-7.0 does type_caster just fine, no bugs there, but the property with the type differs:
|
53
|
+
# 5.2 has "types" as public, 6.0 "types" as private, and >= 6.1 "klass" as private.
|
54
|
+
((tc = send(:type_caster)) && tc.instance_variable_get(:@types)) ||
|
55
|
+
tc.send(:klass)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
40
60
|
module ActiveRecord
|
41
61
|
class Base
|
62
|
+
def self._assoc_names
|
63
|
+
@_assoc_names ||= {}
|
64
|
+
end
|
65
|
+
|
42
66
|
def self.is_view?
|
43
67
|
false
|
44
68
|
end
|
45
69
|
|
46
70
|
# Used to show a little prettier name for an object
|
47
|
-
def
|
48
|
-
klass = self.class
|
49
|
-
# If available, parse simple DSL attached to a model in order to provide a friendlier name.
|
50
|
-
# Object property names can be referenced in square brackets like this:
|
51
|
-
# { 'User' => '[profile.firstname] [profile.lastname]' }
|
52
|
-
|
71
|
+
def self.brick_get_dsl
|
53
72
|
# If there's no DSL yet specified, just try to find the first usable column on this model
|
54
|
-
unless ::Brick.config.model_descrips[
|
55
|
-
descrip_col = (
|
73
|
+
unless (dsl = ::Brick.config.model_descrips[name])
|
74
|
+
descrip_col = (columns.map(&:name) - _brick_get_fks -
|
56
75
|
(::Brick.config.metadata_columns || []) -
|
57
|
-
[
|
58
|
-
::Brick.config.model_descrips[
|
76
|
+
[primary_key]).first
|
77
|
+
dsl = ::Brick.config.model_descrips[name] = "[#{descrip_col}]" if descrip_col
|
78
|
+
end
|
79
|
+
dsl
|
80
|
+
end
|
81
|
+
|
82
|
+
# Pass in true or a JoinArray
|
83
|
+
def self.brick_parse_dsl(build_array = nil, prefix = [], translations = {})
|
84
|
+
build_array = ::Brick::JoinArray.new.tap { |ary| ary.replace([build_array]) } if build_array.is_a?(::Brick::JoinHash)
|
85
|
+
build_array = ::Brick::JoinArray.new unless build_array.nil? || build_array.is_a?(Array)
|
86
|
+
members = []
|
87
|
+
bracket_name = nil
|
88
|
+
prefix = [prefix] unless prefix.is_a?(Array)
|
89
|
+
if (dsl = ::Brick.config.model_descrips[name] || brick_get_dsl)
|
90
|
+
klass = nil
|
91
|
+
dsl.each_char do |ch|
|
92
|
+
if bracket_name
|
93
|
+
if ch == ']' # Time to process a bracketed thing?
|
94
|
+
parts = bracket_name.split('.')
|
95
|
+
first_parts = parts[0..-2].map { |part| klass = klass.reflect_on_association(part_sym = part.to_sym).klass; part_sym }
|
96
|
+
parts = prefix + first_parts + [parts[-1]]
|
97
|
+
if parts.length > 1
|
98
|
+
s = build_array
|
99
|
+
parts[0..-3].each { |v| s = s[v.to_sym] }
|
100
|
+
s[parts[-2]] = nil # unless parts[-2].empty? # Using []= will "hydrate" any missing part(s) in our whole series
|
101
|
+
translations[parts[0..-2].join('.')] = klass
|
102
|
+
end
|
103
|
+
members << parts
|
104
|
+
bracket_name = nil
|
105
|
+
else
|
106
|
+
bracket_name << ch
|
107
|
+
end
|
108
|
+
elsif ch == '['
|
109
|
+
bracket_name = +''
|
110
|
+
klass = self
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else # With no DSL available, still put this prefix into the JoinArray so we can get primary key (ID) info from this table
|
114
|
+
x = prefix.each_with_object(build_array) { |v, s| s[v.to_sym] }
|
115
|
+
x[prefix[-1]] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
|
59
116
|
end
|
60
|
-
|
117
|
+
members
|
118
|
+
end
|
119
|
+
|
120
|
+
# If available, parse simple DSL attached to a model in order to provide a friendlier name.
|
121
|
+
# Object property names can be referenced in square brackets like this:
|
122
|
+
# { 'User' => '[profile.firstname] [profile.lastname]' }
|
123
|
+
def brick_descrip
|
124
|
+
self.class.brick_descrip(self)
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.brick_descrip(obj, data = nil, pk_alias = nil)
|
128
|
+
if (dsl = ::Brick.config.model_descrips[(klass = self).name] || klass.brick_get_dsl)
|
129
|
+
idx = -1
|
61
130
|
caches = {}
|
62
131
|
output = +''
|
63
132
|
is_brackets_have_content = false
|
@@ -65,18 +134,23 @@ module ActiveRecord
|
|
65
134
|
dsl.each_char do |ch|
|
66
135
|
if bracket_name
|
67
136
|
if ch == ']' # Time to process a bracketed thing?
|
68
|
-
|
69
|
-
|
70
|
-
bracket_name.split('.').each do |part|
|
71
|
-
obj_name += ".#{part}"
|
72
|
-
obj = if caches.key?(obj_name)
|
73
|
-
caches[obj_name]
|
137
|
+
datum = if data
|
138
|
+
data[idx += 1].to_s
|
74
139
|
else
|
75
|
-
|
140
|
+
obj_name = +''
|
141
|
+
this_obj = obj
|
142
|
+
bracket_name.split('.').each do |part|
|
143
|
+
obj_name += ".#{part}"
|
144
|
+
this_obj = if caches.key?(obj_name)
|
145
|
+
caches[obj_name]
|
146
|
+
else
|
147
|
+
(caches[obj_name] = this_obj&.send(part.to_sym))
|
148
|
+
end
|
149
|
+
end
|
150
|
+
this_obj&.to_s || ''
|
76
151
|
end
|
77
|
-
|
78
|
-
|
79
|
-
output << (obj&.to_s || '')
|
152
|
+
is_brackets_have_content = true unless (datum).blank?
|
153
|
+
output << (datum || '')
|
80
154
|
bracket_name = nil
|
81
155
|
else
|
82
156
|
bracket_name << ch
|
@@ -91,13 +165,25 @@ module ActiveRecord
|
|
91
165
|
end
|
92
166
|
if is_brackets_have_content
|
93
167
|
output
|
94
|
-
elsif
|
95
|
-
|
168
|
+
elsif pk_alias
|
169
|
+
if (id = pk_alias.map { |pk_alias_part| obj.send(pk_alias_part) })
|
170
|
+
"#{klass.name} ##{id.join(', ')}"
|
171
|
+
end
|
172
|
+
# elsif klass.primary_key
|
173
|
+
# "#{klass.name} ##{obj.send(klass.primary_key)}"
|
96
174
|
else
|
97
|
-
to_s
|
175
|
+
obj.to_s
|
98
176
|
end
|
99
177
|
end
|
100
178
|
|
179
|
+
def self.bt_link(assoc_name)
|
180
|
+
model_underscore = name.underscore
|
181
|
+
assoc_name = CGI.escapeHTML(assoc_name.to_s)
|
182
|
+
model_path = Rails.application.routes.url_helpers.send("#{model_underscore.pluralize}_path".to_sym)
|
183
|
+
link = Class.new.extend(ActionView::Helpers::UrlHelper).link_to(name, model_path)
|
184
|
+
model_underscore == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
|
185
|
+
end
|
186
|
+
|
101
187
|
private
|
102
188
|
|
103
189
|
def self._brick_get_fks
|
@@ -106,9 +192,114 @@ module ActiveRecord
|
|
106
192
|
end
|
107
193
|
|
108
194
|
class Relation
|
109
|
-
|
195
|
+
attr_reader :_brick_chains
|
196
|
+
|
197
|
+
# CLASS STUFF
|
198
|
+
def _recurse_arel(piece, prefix = '')
|
199
|
+
names = []
|
200
|
+
# Our JOINs mashup of nested arrays and hashes
|
201
|
+
# binding.pry if defined?(@arel)
|
202
|
+
case piece
|
203
|
+
when Array
|
204
|
+
names += piece.inject([]) { |s, v| s + _recurse_arel(v, prefix) }
|
205
|
+
when Hash
|
206
|
+
names += piece.inject([]) do |s, v|
|
207
|
+
new_prefix = "#{prefix}#{v.first}_"
|
208
|
+
s << [v.last.shift, new_prefix]
|
209
|
+
s + _recurse_arel(v.last, new_prefix)
|
210
|
+
end
|
211
|
+
|
212
|
+
# ActiveRecord AREL objects
|
213
|
+
when Arel::Nodes::Join # INNER or OUTER JOIN
|
214
|
+
# rubocop:disable Style/IdenticalConditionalBranches
|
215
|
+
if piece.right.is_a?(Arel::Table) # Came in from AR < 3.2?
|
216
|
+
# Arel 2.x and older is a little curious because these JOINs work "back to front".
|
217
|
+
# The left side here is either another earlier JOIN, or at the end of the whole tree, it is
|
218
|
+
# the first table.
|
219
|
+
names += _recurse_arel(piece.left)
|
220
|
+
# The right side here at the top is the very last table, and anywhere else down the tree it is
|
221
|
+
# the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
|
222
|
+
# from the left side.)
|
223
|
+
names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
|
224
|
+
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
225
|
+
# The left side is the "JOIN" table
|
226
|
+
names += _recurse_arel(piece.left)
|
227
|
+
# The expression on the right side is the "ON" clause
|
228
|
+
# on = piece.right.expr
|
229
|
+
# # Find the table which is not ourselves, and thus must be the "path" that led us here
|
230
|
+
# parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
|
231
|
+
# binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
|
232
|
+
table = piece.left
|
233
|
+
if table.is_a?(Arel::Nodes::TableAlias)
|
234
|
+
alias_name = table.right
|
235
|
+
table = table.left
|
236
|
+
end
|
237
|
+
(_brick_chains[table._arel_table_type] ||= []) << (alias_name || table.table_alias || table.name)
|
238
|
+
# puts "YES! #{self.object_id}"
|
239
|
+
end
|
240
|
+
# rubocop:enable Style/IdenticalConditionalBranches
|
241
|
+
when Arel::Table # Table
|
242
|
+
names << [piece._arel_table_type, (piece.table_alias || piece.name)]
|
243
|
+
when Arel::Nodes::TableAlias # Alias
|
244
|
+
# Can get the real table name from: self._recurse_arel(piece.left)
|
245
|
+
names << [piece.left._arel_table_type, piece.right.to_s] # This is simply a string; the alias name itself
|
246
|
+
when Arel::Nodes::JoinSource # Leaving this until the end because AR < 3.2 doesn't know at all about JoinSource!
|
247
|
+
# Spin up an empty set of Brick alias name chains at the start
|
248
|
+
@_brick_chains = {}
|
249
|
+
# The left side is the "FROM" table
|
250
|
+
# names += _recurse_arel(piece.left)
|
251
|
+
names << [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)]
|
252
|
+
# The right side is an array of all JOINs
|
253
|
+
piece.right.each { |join| names << _recurse_arel(join) }
|
254
|
+
end
|
255
|
+
names
|
256
|
+
end
|
257
|
+
|
258
|
+
# INSTANCE STUFF
|
259
|
+
def _arel_alias_names
|
260
|
+
# %%% If with Rails 3.1 and older you get "NoMethodError: undefined method `eq' for nil:NilClass"
|
261
|
+
# when trying to call relation.arel, then somewhere along the line while navigating a has_many
|
262
|
+
# relationship it can't find the proper foreign key.
|
263
|
+
core = arel.ast.cores.first
|
264
|
+
# Accommodate AR < 3.2
|
265
|
+
if core.froms.is_a?(Arel::Table)
|
266
|
+
# All recent versions of AR have #source which brings up an Arel::Nodes::JoinSource
|
267
|
+
_recurse_arel(core.source)
|
268
|
+
else
|
269
|
+
# With AR < 3.2, "froms" brings up the top node, an Arel::Nodes::InnerJoin
|
270
|
+
_recurse_arel(core.froms)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def brick_select(params, selects = nil, bt_descrip = {}, hm_counts = {}, join_array = ::Brick::JoinArray.new
|
275
|
+
# , is_add_bts, is_add_hms
|
276
|
+
)
|
277
|
+
is_add_bts = is_add_hms = true
|
278
|
+
|
279
|
+
# %%% Skip the metadata columns
|
280
|
+
if selects&.empty? # Default to all columns
|
281
|
+
columns.each do |col|
|
282
|
+
selects << "#{table.name}.#{col.name}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Search for BT, HM, and HMT DSL stuff
|
287
|
+
translations = {}
|
288
|
+
if is_add_bts || is_add_hms
|
289
|
+
bts, hms, associatives = ::Brick.get_bts_and_hms(klass)
|
290
|
+
bts.each do |_k, bt|
|
291
|
+
# join_array will receive this relation name when calling #brick_parse_dsl
|
292
|
+
bt_descrip[bt.first] = [bt.last, bt.last.brick_parse_dsl(join_array, bt.first, translations)]
|
293
|
+
end
|
294
|
+
skip_klass_hms = ::Brick.config.skip_index_hms[klass.name] || {}
|
295
|
+
hms.each do |k, hm|
|
296
|
+
next if skip_klass_hms.key?(k)
|
297
|
+
|
298
|
+
hm_counts[k] = hm
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
110
302
|
wheres = {}
|
111
|
-
rel_joins = []
|
112
303
|
params.each do |k, v|
|
113
304
|
case (ks = k.split('.')).length
|
114
305
|
when 1
|
@@ -116,23 +307,87 @@ module ActiveRecord
|
|
116
307
|
when 2
|
117
308
|
assoc_name = ks.first.to_sym
|
118
309
|
# Make sure it's a good association name and that the model has that column name
|
119
|
-
next unless klass.reflect_on_association(assoc_name)&.klass&.columns&.
|
310
|
+
next unless klass.reflect_on_association(assoc_name)&.klass&.columns&.any? { |col| col.name == ks.last }
|
120
311
|
|
121
|
-
|
312
|
+
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
122
313
|
end
|
123
314
|
wheres[k] = v.split(',')
|
124
315
|
end
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
316
|
+
|
317
|
+
if join_array.present?
|
318
|
+
left_outer_joins!(join_array) # joins!(join_array)
|
319
|
+
# Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
|
320
|
+
(rel_dupe = dup)._arel_alias_names
|
321
|
+
core_selects = selects.dup
|
322
|
+
chains = rel_dupe._brick_chains
|
323
|
+
id_for_tables = Hash.new { |h, k| h[k] = [] }
|
324
|
+
field_tbl_names = Hash.new { |h, k| h[k] = {} }
|
325
|
+
bt_columns = bt_descrip.each_with_object([]) do |v, s|
|
326
|
+
tbl_name = field_tbl_names[v.first][v.last.first] ||= shift_or_first(chains[v.last.first])
|
327
|
+
if (id_col = v.last.first.primary_key) && !id_for_tables.key?(v.first) # was tbl_name
|
328
|
+
# Accommodate composite primary key by allowing id_col to come in as an array
|
329
|
+
(id_col.is_a?(Array) ? id_col : [id_col]).each do |id_part|
|
330
|
+
selects << "#{"#{tbl_name}.#{id_part}"} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
|
331
|
+
id_for_tables[v.first] << id_alias
|
332
|
+
end
|
333
|
+
v.last << id_for_tables[v.first]
|
334
|
+
end
|
335
|
+
if (col_name = v.last[1].last&.last)
|
336
|
+
field_tbl_name = nil
|
337
|
+
v.last[1].map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
|
338
|
+
field_tbl_name ||= field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])
|
339
|
+
# col_name is weak when there are multiple, using sel_col.last instead
|
340
|
+
selects << "#{"#{field_tbl_name}.#{sel_col.last}"} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
|
341
|
+
v.last[1][idx] << col_alias
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
join_array.each do |assoc_name|
|
346
|
+
# %%% Need to support {user: :profile}
|
347
|
+
next unless assoc_name.is_a?(Symbol)
|
348
|
+
|
349
|
+
table_alias = shift_or_first(chains[klass = reflect_on_association(assoc_name)&.klass])
|
350
|
+
_assoc_names[assoc_name] = [table_alias, klass]
|
351
|
+
end
|
352
|
+
end
|
353
|
+
# Add derived table JOIN for the has_many counts
|
354
|
+
hm_counts.each do |k, hm|
|
355
|
+
associative = nil
|
356
|
+
count_column = if hm.options[:through]
|
357
|
+
fk_col = (associative = associatives[hm.name]).foreign_key
|
358
|
+
hm.foreign_key
|
359
|
+
else
|
360
|
+
fk_col = hm.foreign_key
|
361
|
+
hm.klass.primary_key || '*'
|
362
|
+
end
|
363
|
+
tbl_alias = "_br_#{hm.name}"
|
364
|
+
pri_tbl = hm.active_record
|
365
|
+
if fk_col.is_a?(Array) # Composite key?
|
366
|
+
on_clause = []
|
367
|
+
fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_tbl.primary_key[idx]}" }
|
368
|
+
joins!("LEFT OUTER
|
369
|
+
JOIN (SELECT #{fk_col.join(', ')}, COUNT(#{count_column}) AS _ct_ FROM #{associative&.name || hm.klass.table_name} GROUP BY #{(1..fk_col.length).to_a.join(', ')}) AS #{tbl_alias}
|
370
|
+
ON #{on_clause.join(' AND ')}")
|
371
|
+
else
|
372
|
+
joins!("LEFT OUTER
|
373
|
+
JOIN (SELECT #{fk_col}, COUNT(#{count_column}) AS _ct_ FROM #{associative&.name || hm.klass.table_name} GROUP BY 1) AS #{tbl_alias}
|
374
|
+
ON #{tbl_alias}.#{fk_col} = #{pri_tbl.table_name}.#{pri_tbl.primary_key}")
|
375
|
+
end
|
129
376
|
end
|
377
|
+
where!(wheres) unless wheres.empty?
|
378
|
+
wheres unless wheres.empty? # Return the specific parameters that we did use
|
379
|
+
end
|
380
|
+
|
381
|
+
private
|
382
|
+
|
383
|
+
def shift_or_first(ary)
|
384
|
+
ary.length > 1 ? ary.shift : ary.first
|
130
385
|
end
|
131
386
|
end
|
132
387
|
|
133
388
|
module Inheritance
|
134
389
|
module ClassMethods
|
135
|
-
|
390
|
+
private
|
136
391
|
|
137
392
|
alias _brick_find_sti_class find_sti_class
|
138
393
|
def find_sti_class(type_name)
|
@@ -145,10 +400,8 @@ module ActiveRecord
|
|
145
400
|
module_prefixes = type_name.split('::')
|
146
401
|
module_prefixes.unshift('') unless module_prefixes.first.blank?
|
147
402
|
module_name = module_prefixes[0..-2].join('::')
|
148
|
-
if ::Brick.config.sti_namespace_prefixes&.key?("::#{module_name}::") ||
|
149
|
-
|
150
|
-
_brick_find_sti_class(type_name)
|
151
|
-
elsif File.exist?(candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
|
403
|
+
if (snp = ::Brick.config.sti_namespace_prefixes)&.key?("::#{module_name}::") || snp&.key?("#{module_name}::") ||
|
404
|
+
File.exist?(candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
|
152
405
|
_brick_find_sti_class(type_name) # Find this STI class normally
|
153
406
|
else
|
154
407
|
# Build missing prefix modules if they don't yet exist
|
@@ -227,10 +480,9 @@ class Object
|
|
227
480
|
end
|
228
481
|
|
229
482
|
relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
|
230
|
-
|
231
|
-
result = if is_controllers_enabled && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
483
|
+
result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
232
484
|
# Otherwise now it's up to us to fill in the gaps
|
233
|
-
if (model =
|
485
|
+
if (model = plural_class_name.singularize.constantize)
|
234
486
|
# if it's a controller and no match or a model doesn't really use the same table name, eager load all models and try to find a model class of the right name.
|
235
487
|
build_controller(class_name, plural_class_name, model, relations)
|
236
488
|
end
|
@@ -281,6 +533,7 @@ class Object
|
|
281
533
|
end
|
282
534
|
return
|
283
535
|
end
|
536
|
+
|
284
537
|
if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
|
285
538
|
is_sti = true
|
286
539
|
else
|
@@ -328,27 +581,30 @@ class Object
|
|
328
581
|
# Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
|
329
582
|
hmts = fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts|
|
330
583
|
# The key in each hash entry (fk.first) is the constraint name
|
331
|
-
|
332
|
-
inverse_assoc_name = assoc[:inverse]&.fetch(:assoc_name, nil)
|
584
|
+
inverse_assoc_name = (assoc = fk.last)[:inverse]&.fetch(:assoc_name, nil)
|
333
585
|
options = {}
|
334
586
|
singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
|
335
587
|
macro = if assoc[:is_bt]
|
336
588
|
# Try to take care of screwy names if this is a belongs_to going to an STI subclass
|
337
|
-
if (primary_class = assoc.fetch(:primary_class, nil)) &&
|
338
|
-
|
339
|
-
|
340
|
-
|
589
|
+
assoc_name = if (primary_class = assoc.fetch(:primary_class, nil)) &&
|
590
|
+
sti_inverse_assoc = primary_class.reflect_on_all_associations.find do |a|
|
591
|
+
a.macro == :has_many && a.options[:class_name] == self.name && assoc[:fk] = a.foreign_key
|
592
|
+
end
|
593
|
+
sti_inverse_assoc.options[:inverse_of]&.to_s || assoc_name
|
594
|
+
else
|
595
|
+
assoc[:assoc_name]
|
596
|
+
end
|
341
597
|
need_class_name = singular_table_name.underscore != assoc_name
|
342
598
|
need_fk = "#{assoc_name}_id" != assoc[:fk]
|
343
599
|
if (inverse = assoc[:inverse])
|
344
600
|
inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], inverse)
|
345
601
|
if (has_ones = ::Brick.config.has_ones&.fetch(inverse[:alternate_name].camelize, nil))&.key?(singular_inv_assoc_name = ActiveSupport::Inflector.singularize(inverse_assoc_name))
|
346
602
|
inverse_assoc_name = if has_ones[singular_inv_assoc_name]
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
603
|
+
need_inverse_of = true
|
604
|
+
has_ones[singular_inv_assoc_name]
|
605
|
+
else
|
606
|
+
singular_inv_assoc_name
|
607
|
+
end
|
352
608
|
end
|
353
609
|
end
|
354
610
|
:belongs_to
|
@@ -359,12 +615,12 @@ class Object
|
|
359
615
|
need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
|
360
616
|
# fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
|
361
617
|
if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
|
362
|
-
assoc_name = if has_ones[singular_assoc_name]
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
618
|
+
assoc_name = if (custom_assoc_name = has_ones[singular_assoc_name])
|
619
|
+
need_class_name = custom_assoc_name != singular_assoc_name
|
620
|
+
custom_assoc_name
|
621
|
+
else
|
622
|
+
singular_assoc_name
|
623
|
+
end
|
368
624
|
:has_one
|
369
625
|
else
|
370
626
|
:has_many
|
@@ -442,13 +698,21 @@ class Object
|
|
442
698
|
|
443
699
|
code << " def index\n"
|
444
700
|
code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect})" : '.all'}\n"
|
445
|
-
code << " @#{table_name}.
|
701
|
+
code << " @#{table_name}.brick_select(params)\n"
|
446
702
|
code << " end\n"
|
447
703
|
self.define_method :index do
|
448
704
|
::Brick.set_db_schema(params)
|
449
|
-
ar_relation = model.primary_key ? model.order(model.primary_key) : model.all
|
450
|
-
|
451
|
-
|
705
|
+
ar_relation = model.all # model.primary_key ? model.order(model.primary_key) : model.all
|
706
|
+
@_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
|
707
|
+
# %%% Add custom HM count columns
|
708
|
+
# %%% What happens when the PK is composite?
|
709
|
+
counts = hm_counts.each_with_object([]) { |v, s| s << "_br_#{v.first}._ct_ AS _br_#{v.first}_ct" }
|
710
|
+
# *selects,
|
711
|
+
instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
712
|
+
# binding.pry
|
713
|
+
@_brick_bt_descrip = bt_descrip
|
714
|
+
@_brick_hm_counts = hm_counts
|
715
|
+
@_brick_join_array = join_array
|
452
716
|
end
|
453
717
|
|
454
718
|
if model.primary_key
|
@@ -500,7 +764,8 @@ class Object
|
|
500
764
|
|
501
765
|
def _brick_get_hm_assoc_name(relation, hm_assoc)
|
502
766
|
if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
|
503
|
-
|
767
|
+
plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
|
768
|
+
[hm_assoc[:alternate_name] == name.underscore ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural, true]
|
504
769
|
else
|
505
770
|
[ActiveSupport::Inflector.pluralize(hm_assoc[:inverse_table]), nil]
|
506
771
|
end
|
@@ -757,18 +1022,16 @@ module Brick
|
|
757
1022
|
|
758
1023
|
return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
|
759
1024
|
|
760
|
-
|
761
|
-
if (assoc_hm = hms.fetch(cnstr_name, nil))
|
1025
|
+
if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
|
762
1026
|
assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
|
763
1027
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
764
1028
|
assoc_hm[:inverse] = assoc_bt
|
765
1029
|
else
|
766
|
-
assoc_hm = hms[
|
1030
|
+
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0], inverse: assoc_bt }
|
767
1031
|
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
768
1032
|
hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
|
769
1033
|
end
|
770
1034
|
assoc_bt[:inverse] = assoc_hm
|
771
|
-
# hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
|
772
1035
|
end
|
773
1036
|
end
|
774
1037
|
end
|