brick 1.0.17 → 1.0.20
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 +1 -1
- data/lib/brick/extensions.rb +347 -74
- data/lib/brick/frameworks/rails/engine.rb +140 -109
- data/lib/brick/join_array.rb +227 -0
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +36 -2
- data/lib/generators/brick/install_generator.rb +10 -10
- 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: 41b64e2d571540382ce5ecd497f1e1413fde20119d46f1b24e1fae5af428fb45
|
4
|
+
data.tar.gz: 1c26ccf7b8df54fd121546e8daeeb9508561328186af46aea8ba1bd819524532
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e103d061b74402f3fe0b944d06b30c20f053166c706a54f15753afc8cb0c703ae71f83bd88191f985a37180cb9bdafdba918178174df08f58f683a723fcfb73a
|
7
|
+
data.tar.gz: 13a7dce7f4f5789a0d7a0c6cae4adcaf2f44d22d0f59c355d75102a7f08292d80c781f8d33be121801d375fd33fed8bcdf4ef6b56108ef8f4ab4110c1f49e296
|
data/lib/brick/config.rb
CHANGED
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
|
59
78
|
end
|
60
|
-
|
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
|
116
|
+
end
|
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,10 +165,14 @@ 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 = obj.send(pk_alias))
|
170
|
+
"#{klass.name} ##{id}"
|
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
|
|
@@ -106,9 +184,91 @@ module ActiveRecord
|
|
106
184
|
end
|
107
185
|
|
108
186
|
class Relation
|
109
|
-
|
187
|
+
attr_reader :_brick_chains
|
188
|
+
|
189
|
+
# CLASS STUFF
|
190
|
+
def _recurse_arel(piece, prefix = '')
|
191
|
+
names = []
|
192
|
+
# Our JOINs mashup of nested arrays and hashes
|
193
|
+
# binding.pry if defined?(@arel)
|
194
|
+
case piece
|
195
|
+
when Array
|
196
|
+
names += piece.inject([]) { |s, v| s + _recurse_arel(v, prefix) }
|
197
|
+
when Hash
|
198
|
+
names += piece.inject([]) do |s, v|
|
199
|
+
new_prefix = "#{prefix}#{v.first}_"
|
200
|
+
s << [v.last.shift, new_prefix]
|
201
|
+
s + _recurse_arel(v.last, new_prefix)
|
202
|
+
end
|
203
|
+
|
204
|
+
# ActiveRecord AREL objects
|
205
|
+
when Arel::Nodes::Join # INNER or OUTER JOIN
|
206
|
+
# rubocop:disable Style/IdenticalConditionalBranches
|
207
|
+
if piece.right.is_a?(Arel::Table) # Came in from AR < 3.2?
|
208
|
+
# Arel 2.x and older is a little curious because these JOINs work "back to front".
|
209
|
+
# The left side here is either another earlier JOIN, or at the end of the whole tree, it is
|
210
|
+
# the first table.
|
211
|
+
names += _recurse_arel(piece.left)
|
212
|
+
# The right side here at the top is the very last table, and anywhere else down the tree it is
|
213
|
+
# the later "JOIN" table of this pair. (The table that comes after all the rest of the JOINs
|
214
|
+
# from the left side.)
|
215
|
+
names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
|
216
|
+
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
217
|
+
# The left side is the "JOIN" table
|
218
|
+
names += _recurse_arel(piece.left)
|
219
|
+
# The expression on the right side is the "ON" clause
|
220
|
+
# on = piece.right.expr
|
221
|
+
# # Find the table which is not ourselves, and thus must be the "path" that led us here
|
222
|
+
# parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
|
223
|
+
# binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
|
224
|
+
table = piece.left
|
225
|
+
if table.is_a?(Arel::Nodes::TableAlias)
|
226
|
+
alias_name = table.right
|
227
|
+
table = table.left
|
228
|
+
end
|
229
|
+
(_brick_chains[table._arel_table_type] ||= []) << (alias_name || table.table_alias || table.name)
|
230
|
+
# puts "YES! #{self.object_id}"
|
231
|
+
end
|
232
|
+
# rubocop:enable Style/IdenticalConditionalBranches
|
233
|
+
when Arel::Table # Table
|
234
|
+
names << [piece._arel_table_type, (piece.table_alias || piece.name)]
|
235
|
+
when Arel::Nodes::TableAlias # Alias
|
236
|
+
# Can get the real table name from: self._recurse_arel(piece.left)
|
237
|
+
names << [piece.left._arel_table_type, piece.right.to_s] # This is simply a string; the alias name itself
|
238
|
+
when Arel::Nodes::JoinSource # Leaving this until the end because AR < 3.2 doesn't know at all about JoinSource!
|
239
|
+
# Spin up an empty set of Brick alias name chains at the start
|
240
|
+
@_brick_chains = {}
|
241
|
+
# The left side is the "FROM" table
|
242
|
+
# names += _recurse_arel(piece.left)
|
243
|
+
names << [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)]
|
244
|
+
# The right side is an array of all JOINs
|
245
|
+
piece.right.each { |join| names << _recurse_arel(join) }
|
246
|
+
end
|
247
|
+
names
|
248
|
+
end
|
249
|
+
|
250
|
+
# INSTANCE STUFF
|
251
|
+
def _arel_alias_names
|
252
|
+
# %%% If with Rails 3.1 and older you get "NoMethodError: undefined method `eq' for nil:NilClass"
|
253
|
+
# when trying to call relation.arel, then somewhere along the line while navigating a has_many
|
254
|
+
# relationship it can't find the proper foreign key.
|
255
|
+
core = arel.ast.cores.first
|
256
|
+
# Accommodate AR < 3.2
|
257
|
+
if core.froms.is_a?(Arel::Table)
|
258
|
+
# All recent versions of AR have #source which brings up an Arel::Nodes::JoinSource
|
259
|
+
_recurse_arel(core.source)
|
260
|
+
else
|
261
|
+
# With AR < 3.2, "froms" brings up the top node, an Arel::Nodes::InnerJoin
|
262
|
+
_recurse_arel(core.froms)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def brick_select(params, selects = nil, bt_descrip = {}, hm_counts = {}, join_array = ::Brick::JoinArray.new
|
267
|
+
# , is_add_bts, is_add_hms
|
268
|
+
)
|
269
|
+
is_add_bts = is_add_hms = true
|
110
270
|
wheres = {}
|
111
|
-
|
271
|
+
has_hm = false
|
112
272
|
params.each do |k, v|
|
113
273
|
case (ks = k.split('.')).length
|
114
274
|
when 1
|
@@ -116,28 +276,90 @@ module ActiveRecord
|
|
116
276
|
when 2
|
117
277
|
assoc_name = ks.first.to_sym
|
118
278
|
# 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&.map(&:name)&.include?(ks.last)
|
120
|
-
|
121
|
-
|
279
|
+
next unless (assoc = klass.reflect_on_association(assoc_name))&.klass&.columns&.map(&:name)&.include?(ks.last)
|
280
|
+
|
281
|
+
# So that we can map an association name to any special alias name used in an AREL query
|
282
|
+
ans = (assoc.klass._assoc_names[assoc_name] ||= [])
|
283
|
+
ans << assoc.klass unless ans.include?(assoc.klass)
|
284
|
+
# There is some potential for duplicates when there is an HM-based where in play. De-duplicate if so.
|
285
|
+
has_hm ||= assoc.macro == :has_many
|
286
|
+
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
122
287
|
end
|
123
288
|
wheres[k] = v.split(',')
|
124
289
|
end
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
290
|
+
# distinct! if has_hm
|
291
|
+
|
292
|
+
# %%% Skip the metadata columns
|
293
|
+
if selects&.empty? # Default to all columns
|
294
|
+
columns.each do |col|
|
295
|
+
selects << "#{table.name}.#{col.name}"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# Search for BT, HM, and HMT DSL stuff
|
300
|
+
translations = {}
|
301
|
+
if is_add_bts || is_add_hms
|
302
|
+
bts, hms, associatives = ::Brick.get_bts_and_hms(klass)
|
303
|
+
bts.each do |_k, bt|
|
304
|
+
# join_array[bt.first] = nil # Store this relation name in our special collection for .joins()
|
305
|
+
bt_descrip[bt.first] = [bt.last, bt.last.brick_parse_dsl(join_array, bt.first, translations)]
|
306
|
+
end
|
307
|
+
hms.each do |k, hm|
|
308
|
+
join_array[k] = nil # Store this relation name in our special collection for .joins()
|
309
|
+
hm_counts[k] = nil # Placeholder that will be filled in once we know the proper table alias
|
310
|
+
end
|
311
|
+
end
|
312
|
+
where!(wheres) unless wheres.empty?
|
313
|
+
if join_array.present?
|
314
|
+
left_outer_joins!(join_array) # joins!(join_array)
|
315
|
+
# Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
|
316
|
+
(rel_dupe = dup)._arel_alias_names
|
317
|
+
core_selects = selects.dup
|
318
|
+
groups = []
|
319
|
+
chains = rel_dupe._brick_chains
|
320
|
+
id_for_tables = {}
|
321
|
+
bt_columns = bt_descrip.each_with_object([]) do |v, s|
|
322
|
+
tbl_name = chains[v.last.first].first
|
323
|
+
if (id_col = v.last.first.primary_key) && !id_for_tables.key?(tbl_name)
|
324
|
+
groups << (unaliased = "#{tbl_name}.#{id_col}")
|
325
|
+
selects << "#{unaliased} AS \"#{(id_alias = id_for_tables[tbl_name] = "_brfk_#{v.first}__#{id_col}")}\""
|
326
|
+
v.last << id_alias
|
327
|
+
end
|
328
|
+
if (col_name = v.last[1].last&.last)
|
329
|
+
v.last[1].map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
|
330
|
+
groups << (unaliased = "#{tbl_name = chains[sel_col.first].first}.#{sel_col.last}")
|
331
|
+
# col_name is weak when there are multiple, using sel_col.last instead
|
332
|
+
tbl_name2 = tbl_name.start_with?('public.') ? tbl_name[7..-1] : tbl_name
|
333
|
+
selects << "#{unaliased} AS \"#{(col_alias = "_brfk_#{tbl_name2}__#{sel_col.last}")}\""
|
334
|
+
v.last[1][idx] << col_alias
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
group!(core_selects + groups) if hm_counts.any? # + bt_columns
|
339
|
+
join_array.each do |assoc_name|
|
340
|
+
# %%% Need to support {user: :profile}
|
341
|
+
next unless assoc_name.is_a?(Symbol)
|
342
|
+
|
343
|
+
klass = reflect_on_association(assoc_name)&.klass
|
344
|
+
table_alias = chains[klass].length > 1 ? chains[klass].shift : chains[klass].first
|
345
|
+
_assoc_names[assoc_name] = [table_alias, klass]
|
346
|
+
end
|
347
|
+
# Copy entries over
|
348
|
+
hm_counts.keys.each do |k|
|
349
|
+
hm_counts[k] = _assoc_names[k]
|
350
|
+
end
|
129
351
|
end
|
352
|
+
wheres unless wheres.empty? # Return the specific parameters that we did use
|
130
353
|
end
|
131
354
|
end
|
132
355
|
|
133
356
|
module Inheritance
|
134
357
|
module ClassMethods
|
135
|
-
|
358
|
+
private
|
136
359
|
|
137
360
|
alias _brick_find_sti_class find_sti_class
|
138
361
|
def find_sti_class(type_name)
|
139
362
|
if ::Brick.sti_models.key?(type_name)
|
140
|
-
# puts ['X', self.name, type_name].inspect
|
141
363
|
_brick_find_sti_class(type_name)
|
142
364
|
else
|
143
365
|
# This auto-STI is more of a brute-force approach, building modules where needed
|
@@ -146,10 +368,8 @@ module ActiveRecord
|
|
146
368
|
module_prefixes = type_name.split('::')
|
147
369
|
module_prefixes.unshift('') unless module_prefixes.first.blank?
|
148
370
|
module_name = module_prefixes[0..-2].join('::')
|
149
|
-
if ::Brick.config.sti_namespace_prefixes&.key?("::#{module_name}::") ||
|
150
|
-
|
151
|
-
_brick_find_sti_class(type_name)
|
152
|
-
elsif File.exists?(candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
|
371
|
+
if (snp = ::Brick.config.sti_namespace_prefixes)&.key?("::#{module_name}::") || snp&.key?("#{module_name}::") ||
|
372
|
+
File.exist?(candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
|
153
373
|
_brick_find_sti_class(type_name) # Find this STI class normally
|
154
374
|
else
|
155
375
|
# Build missing prefix modules if they don't yet exist
|
@@ -228,10 +448,9 @@ class Object
|
|
228
448
|
end
|
229
449
|
|
230
450
|
relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
|
231
|
-
|
232
|
-
result = if is_controllers_enabled && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
451
|
+
result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
233
452
|
# Otherwise now it's up to us to fill in the gaps
|
234
|
-
if (model =
|
453
|
+
if (model = plural_class_name.singularize.constantize)
|
235
454
|
# 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.
|
236
455
|
build_controller(class_name, plural_class_name, model, relations)
|
237
456
|
end
|
@@ -243,10 +462,10 @@ class Object
|
|
243
462
|
|
244
463
|
# Adjust for STI if we know of a base model for the requested model name
|
245
464
|
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
465
|
+
base_model.table_name
|
466
|
+
else
|
467
|
+
ActiveSupport::Inflector.pluralize(singular_table_name)
|
468
|
+
end
|
250
469
|
|
251
470
|
# Maybe, just maybe there's a database table that will satisfy this need
|
252
471
|
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
|
@@ -282,6 +501,7 @@ class Object
|
|
282
501
|
end
|
283
502
|
return
|
284
503
|
end
|
504
|
+
|
285
505
|
if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
|
286
506
|
is_sti = true
|
287
507
|
else
|
@@ -443,18 +663,27 @@ class Object
|
|
443
663
|
|
444
664
|
code << " def index\n"
|
445
665
|
code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect})" : '.all'}\n"
|
446
|
-
code << " @#{table_name}.
|
666
|
+
code << " @#{table_name}.brick_select(params)\n"
|
447
667
|
code << " end\n"
|
448
668
|
self.define_method :index do
|
449
669
|
::Brick.set_db_schema(params)
|
450
|
-
ar_relation = model.primary_key ? model.order(model.primary_key) : model.all
|
451
|
-
|
452
|
-
|
670
|
+
ar_relation = model.all # model.primary_key ? model.order(model.primary_key) : model.all
|
671
|
+
@_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
|
672
|
+
# %%% Add custom HM count columns
|
673
|
+
# %%% What happens when the PK is composite?
|
674
|
+
counts = hm_counts.each_with_object([]) { |v, s| s << "COUNT(DISTINCT #{v.last.first}.#{v.last.last.primary_key}) AS _br_#{v.first}_ct" }
|
675
|
+
puts counts.inspect
|
676
|
+
# *selects,
|
677
|
+
instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
678
|
+
# binding.pry
|
679
|
+
@_brick_bt_descrip = bt_descrip
|
680
|
+
@_brick_hm_counts = hm_counts
|
681
|
+
@_brick_join_array = join_array
|
453
682
|
end
|
454
683
|
|
455
684
|
if model.primary_key
|
456
685
|
code << " def show\n"
|
457
|
-
code << " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n"
|
686
|
+
code << (find_by_id = " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n")
|
458
687
|
code << " end\n"
|
459
688
|
self.define_method :show do
|
460
689
|
::Brick.set_db_schema(params)
|
@@ -463,9 +692,36 @@ class Object
|
|
463
692
|
end
|
464
693
|
|
465
694
|
# By default, views get marked as read-only
|
466
|
-
unless (relation = relations[model.table_name]).key?(:isView)
|
467
|
-
code << " # (Define :new, :create
|
468
|
-
|
695
|
+
unless false # model.readonly # (relation = relations[model.table_name]).key?(:isView)
|
696
|
+
code << " # (Define :new, :create)\n"
|
697
|
+
|
698
|
+
if model.primary_key
|
699
|
+
is_need_params = true
|
700
|
+
# code << " # (Define :edit, and :destroy)\n"
|
701
|
+
code << " def update\n"
|
702
|
+
code << find_by_id
|
703
|
+
params_name = "#{singular_table_name}_params"
|
704
|
+
code << " @#{singular_table_name}.update(#{params_name})\n"
|
705
|
+
code << " end\n"
|
706
|
+
self.define_method :update do
|
707
|
+
::Brick.set_db_schema(params)
|
708
|
+
instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(params[:id].split(','))))
|
709
|
+
obj = obj.first if obj.is_a?(Array)
|
710
|
+
obj.send(:update, send(params_name = params_name.to_sym))
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
if is_need_params
|
715
|
+
code << "private\n"
|
716
|
+
code << " def params\n"
|
717
|
+
code << " params.require(:#{singular_table_name}).permit(#{model.columns_hash.keys.map { |c| c.to_sym.inspect }.join(', ')})\n"
|
718
|
+
code << " end\n"
|
719
|
+
self.define_method(params_name) do
|
720
|
+
params.require(singular_table_name.to_sym).permit(model.columns_hash.keys)
|
721
|
+
end
|
722
|
+
private params_name
|
723
|
+
# Get column names for params from relations[model.table_name][:cols].keys
|
724
|
+
end
|
469
725
|
end
|
470
726
|
code << "end # #{class_name}\n\n"
|
471
727
|
end # class definition
|
@@ -495,9 +751,9 @@ module ActiveRecord::ConnectionHandling
|
|
495
751
|
end
|
496
752
|
|
497
753
|
def _brick_reflect_tables
|
498
|
-
|
499
|
-
|
500
|
-
|
754
|
+
if (relations = ::Brick.relations).empty?
|
755
|
+
# Only for Postgres? (Doesn't work in sqlite3)
|
756
|
+
# puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
501
757
|
|
502
758
|
schema_sql = 'SELECT NULL AS table_schema;'
|
503
759
|
case ActiveRecord::Base.connection.adapter_name
|
@@ -587,6 +843,25 @@ module ActiveRecord::ConnectionHandling
|
|
587
843
|
end
|
588
844
|
end
|
589
845
|
|
846
|
+
# # Add unique OIDs
|
847
|
+
# if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
848
|
+
# ActiveRecord::Base.execute_sql(
|
849
|
+
# "SELECT c.oid, n.nspname, c.relname
|
850
|
+
# FROM pg_catalog.pg_namespace AS n
|
851
|
+
# INNER JOIN pg_catalog.pg_class AS c ON n.oid = c.relnamespace
|
852
|
+
# WHERE c.relkind IN ('r', 'v')"
|
853
|
+
# ).each do |r|
|
854
|
+
# next if ['pg_catalog', 'information_schema', ''].include?(r['nspname']) ||
|
855
|
+
# ['ar_internal_metadata', 'schema_migrations'].include?(r['relname'])
|
856
|
+
# relation = relations.fetch(r['relname'], nil)
|
857
|
+
# if relation
|
858
|
+
# (relation[:oid] ||= {})[r['nspname']] = r['oid']
|
859
|
+
# else
|
860
|
+
# puts "Where is #{r['nspname']} #{r['relname']} ?"
|
861
|
+
# end
|
862
|
+
# end
|
863
|
+
# end
|
864
|
+
|
590
865
|
case ActiveRecord::Base.connection.adapter_name
|
591
866
|
when 'PostgreSQL', 'Mysql2'
|
592
867
|
sql = ActiveRecord::Base.send(:sanitize_sql_array, [
|
@@ -629,14 +904,12 @@ module ActiveRecord::ConnectionHandling
|
|
629
904
|
puts "\nClasses that can be built from views:"
|
630
905
|
views.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
|
631
906
|
end
|
907
|
+
|
632
908
|
# Try to load the initializer pretty danged early
|
633
909
|
if File.exist?(brick_initialiser = Rails.root.join('config/initializers/brick.rb'))
|
634
910
|
load brick_initialiser
|
635
911
|
::Brick.load_additional_references
|
636
912
|
end
|
637
|
-
|
638
|
-
# relations.keys.each { |k| ActiveSupport::Inflector.singularize(k).camelize.constantize }
|
639
|
-
# Layout table describes permissioned hierarchy throughout
|
640
913
|
end
|
641
914
|
end
|
642
915
|
|
@@ -681,7 +954,7 @@ module Brick
|
|
681
954
|
missing << fk[0] unless relations.key?(fk[0])
|
682
955
|
missing << primary_table unless is_class || relations.key?(primary_table)
|
683
956
|
unless missing.empty?
|
684
|
-
tables = relations.reject { |
|
957
|
+
tables = relations.reject { |_k, v| v.fetch(:isView, nil) }.keys.sort
|
685
958
|
puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
|
686
959
|
return
|
687
960
|
end
|
@@ -690,7 +963,7 @@ module Brick
|
|
690
963
|
puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
|
691
964
|
return
|
692
965
|
end
|
693
|
-
if (redundant = bts.find { |
|
966
|
+
if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == primary_table })
|
694
967
|
if is_class && !redundant.last.key?(:class)
|
695
968
|
redundant.last[:primary_class] = primary_class # Round out this BT so it can find the proper :source for a HMT association that references an STI subclass
|
696
969
|
else
|
@@ -712,19 +985,19 @@ module Brick
|
|
712
985
|
# assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] }
|
713
986
|
end
|
714
987
|
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
assoc_bt[:inverse] = assoc_hm
|
988
|
+
return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
|
989
|
+
|
990
|
+
cnstr_name = "hm_#{cnstr_name}"
|
991
|
+
if (assoc_hm = hms.fetch(cnstr_name, nil))
|
992
|
+
assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
|
993
|
+
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
994
|
+
assoc_hm[:inverse] = assoc_bt
|
995
|
+
else
|
996
|
+
assoc_hm = hms[cnstr_name] = { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0], inverse: assoc_bt }
|
997
|
+
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
998
|
+
hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
|
727
999
|
end
|
1000
|
+
assoc_bt[:inverse] = assoc_hm
|
728
1001
|
# hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
|
729
1002
|
end
|
730
1003
|
end
|