brick 1.0.14 → 1.0.17
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 +20 -4
- data/lib/brick/extensions.rb +87 -55
- data/lib/brick/frameworks/rails/engine.rb +76 -47
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +43 -2
- data/lib/generators/brick/install_generator.rb +17 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f1a45a5262526e69cf49f16773049eb348e3971bddf09ecf05927002a265019
|
4
|
+
data.tar.gz: e9566a423a19ab55d44522b44e8b4abfcc39e92335b6093fe782c6344644bbbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb89dff96dccc7051bc854fe523b03f818255d81e4f2e2a65eb9808115e719b8bad98e38114f15ee79b32b1f035c95c364a74392948cd2e99f4030e6988e0217
|
7
|
+
data.tar.gz: cd5507fde2f1f23481696ab051d14f89e5e7e7d3fc2be97af947ff287313b7ada8ca7b4e3f9e0e54dc2ab7f14e7f39a3c18f78cf7fc995c58b3d60cf68487973
|
data/lib/brick/config.rb
CHANGED
@@ -66,12 +66,12 @@ module Brick
|
|
66
66
|
end
|
67
67
|
|
68
68
|
# Skip creating a has_many association for these
|
69
|
-
def
|
70
|
-
@mutex.synchronize { @
|
69
|
+
def exclude_hms
|
70
|
+
@mutex.synchronize { @exclude_hms }
|
71
71
|
end
|
72
72
|
|
73
|
-
def
|
74
|
-
@mutex.synchronize { @
|
73
|
+
def exclude_hms=(skips)
|
74
|
+
@mutex.synchronize { @exclude_hms = skips }
|
75
75
|
end
|
76
76
|
|
77
77
|
# Associations to treat as a has_one
|
@@ -115,6 +115,14 @@ module Brick
|
|
115
115
|
@mutex.synchronize { @exclude_tables = value }
|
116
116
|
end
|
117
117
|
|
118
|
+
def models_inherit_from
|
119
|
+
@mutex.synchronize { @models_inherit_from }
|
120
|
+
end
|
121
|
+
|
122
|
+
def models_inherit_from=(value)
|
123
|
+
@mutex.synchronize { @models_inherit_from = value }
|
124
|
+
end
|
125
|
+
|
118
126
|
def table_name_prefixes
|
119
127
|
@mutex.synchronize { @table_name_prefixes }
|
120
128
|
end
|
@@ -130,5 +138,13 @@ module Brick
|
|
130
138
|
def metadata_columns=(columns)
|
131
139
|
@mutex.synchronize { @metadata_columns = columns }
|
132
140
|
end
|
141
|
+
|
142
|
+
def not_nullables
|
143
|
+
@mutex.synchronize { @not_nullables }
|
144
|
+
end
|
145
|
+
|
146
|
+
def not_nullables=(columns)
|
147
|
+
@mutex.synchronize { @not_nullables = columns }
|
148
|
+
end
|
133
149
|
end
|
134
150
|
end
|
data/lib/brick/extensions.rb
CHANGED
@@ -137,6 +137,7 @@ module ActiveRecord
|
|
137
137
|
alias _brick_find_sti_class find_sti_class
|
138
138
|
def find_sti_class(type_name)
|
139
139
|
if ::Brick.sti_models.key?(type_name)
|
140
|
+
# puts ['X', self.name, type_name].inspect
|
140
141
|
_brick_find_sti_class(type_name)
|
141
142
|
else
|
142
143
|
# This auto-STI is more of a brute-force approach, building modules where needed
|
@@ -145,8 +146,8 @@ module ActiveRecord
|
|
145
146
|
module_prefixes = type_name.split('::')
|
146
147
|
module_prefixes.unshift('') unless module_prefixes.first.blank?
|
147
148
|
module_name = module_prefixes[0..-2].join('::')
|
148
|
-
if ::Brick.config.sti_namespace_prefixes&.key?(module_name) ||
|
149
|
-
::Brick.config.sti_namespace_prefixes&.key?(module_name
|
149
|
+
if ::Brick.config.sti_namespace_prefixes&.key?("::#{module_name}::") ||
|
150
|
+
::Brick.config.sti_namespace_prefixes&.key?("#{module_name}::")
|
150
151
|
_brick_find_sti_class(type_name)
|
151
152
|
elsif File.exists?(candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
|
152
153
|
_brick_find_sti_class(type_name) # Find this STI class normally
|
@@ -154,15 +155,20 @@ module ActiveRecord
|
|
154
155
|
# Build missing prefix modules if they don't yet exist
|
155
156
|
this_module = Object
|
156
157
|
module_prefixes[1..-2].each do |module_name|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
158
|
+
this_module = if this_module.const_defined?(module_name)
|
159
|
+
this_module.const_get(module_name)
|
160
|
+
else
|
161
|
+
this_module.const_set(module_name.to_sym, Module.new)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
if this_module.const_defined?(class_name = module_prefixes.last.to_sym)
|
165
|
+
this_module.const_get(class_name)
|
166
|
+
else
|
167
|
+
# Build STI subclass and place it into the namespace module
|
168
|
+
# %%% Does this ever get used???
|
169
|
+
puts [this_module.const_set(class_name, klass = Class.new(self)).name, class_name].inspect
|
170
|
+
klass
|
162
171
|
end
|
163
|
-
# Build STI subclass and place it into the namespace module
|
164
|
-
this_module.const_set(module_prefixes.last.to_sym, klass = Class.new(self))
|
165
|
-
klass
|
166
172
|
end
|
167
173
|
end
|
168
174
|
end
|
@@ -170,22 +176,27 @@ module ActiveRecord
|
|
170
176
|
end
|
171
177
|
end
|
172
178
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
::Brick.
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
179
|
+
if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works with previous non-zeitwerk auto-loading
|
180
|
+
module ActiveSupport::Dependencies
|
181
|
+
class << self
|
182
|
+
# %%% Probably a little more targeted than other approaches we've taken thusfar
|
183
|
+
# This happens before the whole parent check
|
184
|
+
alias _brick_autoload_module! autoload_module!
|
185
|
+
def autoload_module!(*args)
|
186
|
+
into, const_name, qualified_name, path_suffix = args
|
187
|
+
if (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)&.constantize)
|
188
|
+
::Brick.sti_models[qualified_name] = { base: base_class }
|
189
|
+
# Build subclass and place it into the specially STI-namespaced module
|
190
|
+
into.const_set(const_name.to_sym, klass = Class.new(base_class))
|
191
|
+
# %%% used to also have: autoload_once_paths.include?(base_path) ||
|
192
|
+
autoloaded_constants << qualified_name unless autoloaded_constants.include?(qualified_name)
|
193
|
+
klass
|
194
|
+
elsif (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{const_name}", nil)&.constantize)
|
195
|
+
# Build subclass and place it into Object
|
196
|
+
Object.const_set(const_name.to_sym, klass = Class.new(base_class))
|
197
|
+
else
|
198
|
+
_brick_autoload_module!(*args)
|
199
|
+
end
|
189
200
|
end
|
190
201
|
end
|
191
202
|
end
|
@@ -246,15 +257,14 @@ class Object
|
|
246
257
|
built_class, code = result
|
247
258
|
puts "\n#{code}"
|
248
259
|
built_class
|
249
|
-
elsif ::Brick.config.sti_namespace_prefixes&.key?(class_name)
|
250
|
-
# binding.pry
|
260
|
+
elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}")
|
251
261
|
# module_prefixes = type_name.split('::')
|
252
262
|
# path = self.name.split('::')[0..-2] + []
|
253
263
|
# module_prefixes.unshift('') unless module_prefixes.first.blank?
|
254
264
|
# candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
|
255
265
|
self._brick_const_missing(*args)
|
256
266
|
else
|
257
|
-
puts "MISSING! #{args.inspect} #{table_name}"
|
267
|
+
puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
|
258
268
|
self._brick_const_missing(*args)
|
259
269
|
end
|
260
270
|
end
|
@@ -267,13 +277,15 @@ class Object
|
|
267
277
|
|
268
278
|
# Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
|
269
279
|
if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
|
270
|
-
|
280
|
+
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.titleize}::")
|
281
|
+
puts "Warning: Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\"."
|
282
|
+
end
|
271
283
|
return
|
272
284
|
end
|
273
285
|
if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
|
274
286
|
is_sti = true
|
275
287
|
else
|
276
|
-
base_model = ActiveRecord::Base
|
288
|
+
base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
|
277
289
|
end
|
278
290
|
code = +"class #{model_name} < #{base_model.name}\n"
|
279
291
|
built_model = Class.new(base_model) do |new_model_class|
|
@@ -322,6 +334,11 @@ class Object
|
|
322
334
|
options = {}
|
323
335
|
singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
|
324
336
|
macro = if assoc[:is_bt]
|
337
|
+
# Try to take care of screwy names if this is a belongs_to going to an STI subclass
|
338
|
+
if (primary_class = assoc.fetch(:primary_class, nil)) &&
|
339
|
+
(sti_inverse_assoc = primary_class.reflect_on_all_associations.find { |a| a.macro == :has_many && a.options[:class_name] == self.name && assoc[:fk] = a.foreign_key })
|
340
|
+
assoc_name = sti_inverse_assoc.options[:inverse_of].to_s || assoc_name
|
341
|
+
end
|
325
342
|
need_class_name = singular_table_name.underscore != assoc_name
|
326
343
|
need_fk = "#{assoc_name}_id" != assoc[:fk]
|
327
344
|
if (inverse = assoc[:inverse])
|
@@ -356,7 +373,7 @@ class Object
|
|
356
373
|
end
|
357
374
|
# Figure out if we need to specially call out the class_name and/or foreign key
|
358
375
|
# (and if either of those then definitely also a specific inverse_of)
|
359
|
-
options[:class_name] = singular_table_name.camelize if need_class_name
|
376
|
+
options[:class_name] = assoc[:primary_class]&.name || singular_table_name.camelize if need_class_name
|
360
377
|
# Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
|
361
378
|
if need_fk # Funky foreign key?
|
362
379
|
options[:foreign_key] = if assoc[:fk].is_a?(Array)
|
@@ -387,12 +404,13 @@ class Object
|
|
387
404
|
fks.each do |fk|
|
388
405
|
source = nil
|
389
406
|
this_hmt_fk = if fks.length > 1
|
390
|
-
singular_assoc_name =
|
407
|
+
singular_assoc_name = fk.first[:inverse][:assoc_name].singularize
|
391
408
|
source = fk.last
|
392
|
-
through =
|
409
|
+
through = fk.first[:alternate_name].pluralize
|
393
410
|
"#{singular_assoc_name}_#{hmt_fk}"
|
394
411
|
else
|
395
|
-
|
412
|
+
source = fk.last unless hmt_fk.singularize == fk.last
|
413
|
+
through = fk.first[:assoc_name].pluralize
|
396
414
|
hmt_fk
|
397
415
|
end
|
398
416
|
code << " has_many :#{this_hmt_fk}, through: #{(assoc_name = through.to_sym).to_sym.inspect}#{", source: :#{source}" if source}\n"
|
@@ -401,6 +419,14 @@ class Object
|
|
401
419
|
self.send(:has_many, this_hmt_fk.to_sym, **options)
|
402
420
|
end
|
403
421
|
end
|
422
|
+
# Not NULLables
|
423
|
+
relation[:cols].each do |col, datatype|
|
424
|
+
if (datatype[3] && ar_pks.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
|
425
|
+
::Brick.config.not_nullables.include?("#{matching}.#{col}")
|
426
|
+
code << " validates :#{col}, presence: true\n"
|
427
|
+
self.send(:validates, col.to_sym, { presence: true })
|
428
|
+
end
|
429
|
+
end
|
404
430
|
end
|
405
431
|
code << "end # model #{model_name}\n\n"
|
406
432
|
end # class definition
|
@@ -496,7 +522,8 @@ module ActiveRecord::ConnectionHandling
|
|
496
522
|
"SELECT t.table_name AS relation_name, t.table_type,
|
497
523
|
c.column_name, c.data_type,
|
498
524
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
499
|
-
tc.constraint_type AS const, kcu.constraint_name AS \"key\"
|
525
|
+
tc.constraint_type AS const, kcu.constraint_name AS \"key\",
|
526
|
+
c.is_nullable
|
500
527
|
FROM INFORMATION_SCHEMA.tables AS t
|
501
528
|
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
502
529
|
AND t.table_name = c.table_name
|
@@ -534,7 +561,7 @@ module ActiveRecord::ConnectionHandling
|
|
534
561
|
end
|
535
562
|
key << col_name if key
|
536
563
|
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
537
|
-
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name)]
|
564
|
+
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
|
538
565
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
539
566
|
end
|
540
567
|
else # MySQL2 acts a little differently, bringing back an array for each row
|
@@ -602,7 +629,11 @@ module ActiveRecord::ConnectionHandling
|
|
602
629
|
puts "\nClasses that can be built from views:"
|
603
630
|
views.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
|
604
631
|
end
|
605
|
-
#
|
632
|
+
# Try to load the initializer pretty danged early
|
633
|
+
if File.exist?(brick_initialiser = Rails.root.join('config/initializers/brick.rb'))
|
634
|
+
load brick_initialiser
|
635
|
+
::Brick.load_additional_references
|
636
|
+
end
|
606
637
|
|
607
638
|
# relations.keys.each { |k| ActiveSupport::Inflector.singularize(k).camelize.constantize }
|
608
639
|
# Layout table describes permissioned hierarchy throughout
|
@@ -638,16 +669,17 @@ module Brick
|
|
638
669
|
bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
|
639
670
|
|
640
671
|
bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
|
641
|
-
|
672
|
+
primary_table = (is_class = fk[2].is_a?(Hash) && fk[2].key?(:class)) ? (primary_class = fk[2][:class].constantize).table_name : fk[2]
|
673
|
+
hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
|
642
674
|
|
643
675
|
unless (cnstr_name = fk[3])
|
644
676
|
# For any appended references (those that come from config), arrive upon a definitely unique constraint name
|
645
|
-
cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{fk[2]}"
|
677
|
+
cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{is_class ? fk[2][:class].underscore : fk[2]}"
|
646
678
|
cnstr_added_num = 1
|
647
679
|
cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
|
648
680
|
missing = []
|
649
681
|
missing << fk[0] unless relations.key?(fk[0])
|
650
|
-
missing <<
|
682
|
+
missing << primary_table unless is_class || relations.key?(primary_table)
|
651
683
|
unless missing.empty?
|
652
684
|
tables = relations.reject { |k, v| v.fetch(:isView, nil) }.keys.sort
|
653
685
|
puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
|
@@ -658,8 +690,12 @@ module Brick
|
|
658
690
|
puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
|
659
691
|
return
|
660
692
|
end
|
661
|
-
if (redundant = bts.find { |k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] ==
|
662
|
-
|
693
|
+
if (redundant = bts.find { |k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == primary_table })
|
694
|
+
if is_class && !redundant.last.key?(:class)
|
695
|
+
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
|
+
else
|
697
|
+
puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed. (Already established by #{redundant.first}.)"
|
698
|
+
end
|
663
699
|
return
|
664
700
|
end
|
665
701
|
end
|
@@ -667,10 +703,16 @@ module Brick
|
|
667
703
|
assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[1]] : assoc_bt[:fk].concat(fk[1])
|
668
704
|
assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
|
669
705
|
else
|
670
|
-
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table:
|
706
|
+
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: primary_table }
|
707
|
+
end
|
708
|
+
if is_class
|
709
|
+
# For use in finding the proper :source for a HMT association that references an STI subclass
|
710
|
+
assoc_bt[:primary_class] = primary_class
|
711
|
+
# For use in finding the proper :inverse_of for a BT association that references an STI subclass
|
712
|
+
# assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] }
|
671
713
|
end
|
672
714
|
|
673
|
-
unless ::Brick.config.
|
715
|
+
unless is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
|
674
716
|
cnstr_name = "hm_#{cnstr_name}"
|
675
717
|
if (assoc_hm = hms.fetch(cnstr_name, nil))
|
676
718
|
assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
|
@@ -685,15 +727,5 @@ module Brick
|
|
685
727
|
end
|
686
728
|
# hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
|
687
729
|
end
|
688
|
-
|
689
|
-
# Rails < 4.0 doesn't have ActiveRecord::RecordNotUnique, so use the more generic ActiveRecord::ActiveRecordError instead
|
690
|
-
ar_not_unique_error = ActiveRecord.const_defined?('RecordNotUnique') ? ActiveRecord::RecordNotUnique : ActiveRecord::ActiveRecordError
|
691
|
-
class NoUniqueColumnError < ar_not_unique_error
|
692
|
-
end
|
693
|
-
|
694
|
-
# Rails < 4.2 doesn't have ActiveRecord::RecordInvalid, so use the more generic ActiveRecord::ActiveRecordError instead
|
695
|
-
ar_invalid_error = ActiveRecord.const_defined?('RecordInvalid') ? ActiveRecord::RecordInvalid : ActiveRecord::ActiveRecordError
|
696
|
-
class LessThanHalfAreMatchingColumnsError < ar_invalid_error
|
697
|
-
end
|
698
730
|
end
|
699
731
|
end
|
@@ -16,17 +16,23 @@ module Brick
|
|
16
16
|
# Specific database tables and views to omit when auto-creating models
|
17
17
|
::Brick.exclude_tables = app.config.brick.fetch(:exclude_tables, [])
|
18
18
|
|
19
|
+
# Class for auto-generated models to inherit from
|
20
|
+
::Brick.models_inherit_from = app.config.brick.fetch(:models_inherit_from, ActiveRecord::Base)
|
21
|
+
|
19
22
|
# When table names have specific prefixes, automatically place them in their own module with a table_name_prefix.
|
20
23
|
::Brick.table_name_prefixes = app.config.brick.fetch(:table_name_prefixes, [])
|
21
24
|
|
22
25
|
# Columns to treat as being metadata for purposes of identifying associative tables for has_many :through
|
23
26
|
::Brick.metadata_columns = app.config.brick.fetch(:metadata_columns, ['created_at', 'updated_at', 'deleted_at'])
|
24
27
|
|
28
|
+
# Columns for which to add a validate presence: true even though the database doesn't have them marked as NOT NULL
|
29
|
+
::Brick.not_nullables = app.config.brick.fetch(:not_nullables, [])
|
30
|
+
|
25
31
|
# Additional references (virtual foreign keys)
|
26
32
|
::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)
|
27
33
|
|
28
34
|
# Skip creating a has_many association for these
|
29
|
-
::Brick.
|
35
|
+
::Brick.exclude_hms = app.config.brick.fetch(:exclude_hms, nil)
|
30
36
|
|
31
37
|
# Has one relationships
|
32
38
|
::Brick.has_ones = app.config.brick.fetch(:has_ones, nil)
|
@@ -70,34 +76,34 @@ module Brick
|
|
70
76
|
bts, hms = ::Brick.get_bts_and_hms(@_brick_model)
|
71
77
|
# Mark has_manys that go to an associative ("join") table so that they are skipped in the UI,
|
72
78
|
# as well as any possible polymorphic associations
|
73
|
-
|
79
|
+
exclude_hms = {}
|
74
80
|
associatives = hms.each_with_object({}) do |hmt, s|
|
75
81
|
if (through = hmt.last.options[:through])
|
76
|
-
|
82
|
+
exclude_hms[through] = nil
|
77
83
|
s[hmt.first] = hms[through] # End up with a hash of HMT names pointing to join-table associations
|
78
84
|
elsif hmt.last.inverse_of.nil?
|
79
85
|
puts "SKIPPING #{hmt.last.name.inspect}"
|
80
86
|
# %%% If we don't do this then below associative.name will find that associative is nil
|
81
|
-
|
87
|
+
exclude_hms[hmt.last.name] = nil
|
82
88
|
end
|
83
89
|
end
|
84
90
|
|
85
91
|
schema_options = ::Brick.db_schemas.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
|
92
|
+
table_options = ::Brick.relations.keys.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
|
86
93
|
hms_columns = +'' # Used for 'index'
|
87
|
-
# puts skip_hms.inspect
|
88
94
|
hms_headers = hms.each_with_object([]) do |hm, s|
|
89
|
-
next if
|
95
|
+
next if exclude_hms.key?((hm_assoc = hm.last).name)
|
90
96
|
|
91
97
|
if args.first == 'index'
|
92
|
-
hm_fk_name = if
|
93
|
-
associative = associatives[
|
98
|
+
hm_fk_name = if hm_assoc.options[:through]
|
99
|
+
associative = associatives[hm_assoc.name]
|
94
100
|
"'#{associative.name}.#{associative.foreign_key}'"
|
95
101
|
else
|
96
|
-
|
102
|
+
hm_assoc.foreign_key
|
97
103
|
end
|
98
|
-
hms_columns << if
|
104
|
+
hms_columns << if hm_assoc.macro == :has_many
|
99
105
|
"<td>
|
100
|
-
<%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{
|
106
|
+
<%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm_assoc.klass.name.underscore.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) unless #{obj_name}.#{hm.first}.count.zero? %>
|
101
107
|
</td>\n"
|
102
108
|
else # has_one
|
103
109
|
"<td>
|
@@ -105,7 +111,7 @@ module Brick
|
|
105
111
|
</td>\n"
|
106
112
|
end
|
107
113
|
end
|
108
|
-
s << [
|
114
|
+
s << [hm_assoc, "H#{hm_assoc.macro == :has_one ? 'O' : 'M'}#{'T' if hm_assoc.options[:through]} #{hm.first}"]
|
109
115
|
end
|
110
116
|
|
111
117
|
css = "<style>
|
@@ -150,10 +156,20 @@ table tbody tr.active-row {
|
|
150
156
|
}
|
151
157
|
|
152
158
|
a.show-arrow {
|
159
|
+
font-size: 1.5em;
|
160
|
+
text-decoration: none;
|
161
|
+
}
|
162
|
+
a.big-arrow {
|
153
163
|
font-size: 2.5em;
|
154
164
|
text-decoration: none;
|
155
165
|
}
|
156
|
-
</style>
|
166
|
+
</style>
|
167
|
+
<% def is_bcrypt?(val)
|
168
|
+
val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
|
169
|
+
end
|
170
|
+
def hide_bcrypt(val)
|
171
|
+
is_bcrypt?(val) ? '(hidden)' : val
|
172
|
+
end %>"
|
157
173
|
|
158
174
|
script = "<script>
|
159
175
|
var schemaSelect = document.getElementById(\"schema\");
|
@@ -168,8 +184,28 @@ if (schemaSelect) {
|
|
168
184
|
location.href = changeout(location.href, \"_brick_schema\", this.value);
|
169
185
|
});
|
170
186
|
}
|
187
|
+
|
188
|
+
var tblSelect = document.getElementById(\"tbl\");
|
189
|
+
if (tblSelect) {
|
190
|
+
tblSelect.value = changeout(location.href);
|
191
|
+
tblSelect.addEventListener(\"change\", function () {
|
192
|
+
var lhr = changeout(location.href, null, this.value);
|
193
|
+
if (brickSchema)
|
194
|
+
lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
|
195
|
+
location.href = lhr;
|
196
|
+
});
|
197
|
+
}
|
198
|
+
|
171
199
|
function changeout(href, param, value) {
|
172
200
|
var hrefParts = href.split(\"?\");
|
201
|
+
if (param === undefined || param === null) {
|
202
|
+
hrefParts = hrefParts[0].split(\"://\");
|
203
|
+
var pathParts = hrefParts[hrefParts.length - 1].split(\"/\");
|
204
|
+
if (value === undefined)
|
205
|
+
return pathParts[1];
|
206
|
+
else
|
207
|
+
return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
|
208
|
+
}
|
173
209
|
var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
|
174
210
|
params = params.reduce(function (s, v) { var parts = v.split(\"=\"); s[parts[0]] = parts[1]; return s; }, {});
|
175
211
|
if (value === undefined) return params[param];
|
@@ -183,6 +219,7 @@ function changeout(href, param, value) {
|
|
183
219
|
"#{css}
|
184
220
|
<p style=\"color: green\"><%= notice %></p>#{"
|
185
221
|
<select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
|
222
|
+
<select id=\"tbl\">#{table_options}</select>
|
186
223
|
<h1>#{model_name.pluralize}</h1>
|
187
224
|
<% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
|
188
225
|
<table id=\"#{table_name}\">
|
@@ -204,7 +241,7 @@ function changeout(href, param, value) {
|
|
204
241
|
<tbody>
|
205
242
|
<% @#{table_name}.each do |#{obj_name}| %>
|
206
243
|
<tr>#{"
|
207
|
-
<td><%= link_to '⇛', #{obj_name}_path(#{obj_name}.#{pk}), { class: '
|
244
|
+
<td><%= link_to '⇛', #{obj_name}_path(#{obj_name}.#{pk}), { class: 'big-arrow' } %></td>" if pk }
|
208
245
|
<% #{obj_name}.attributes.each do |k, val| %>
|
209
246
|
<% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
|
210
247
|
<td>
|
@@ -213,9 +250,9 @@ function changeout(href, param, value) {
|
|
213
250
|
# send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))
|
214
251
|
# Otherwise we get stuff like:
|
215
252
|
# ActionView::Template::Error (undefined method `vehicle_path' for #<ActionView::Base:0x0000000033a888>) %>
|
216
|
-
<%= bt_obj = bt[1].find_by(bt
|
253
|
+
<%= bt_obj = bt[1].find_by(bt[2] => val); link_to(bt_obj.brick_descrip, send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))) if bt_obj %>
|
217
254
|
<% else %>
|
218
|
-
<%= val %>
|
255
|
+
<%= hide_bcrypt(val) %>
|
219
256
|
<% end %>
|
220
257
|
</td>
|
221
258
|
<% end %>
|
@@ -232,40 +269,51 @@ function changeout(href, param, value) {
|
|
232
269
|
"#{css}
|
233
270
|
<p style=\"color: green\"><%= notice %></p>#{"
|
234
271
|
<select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
|
272
|
+
<select id=\"tbl\">#{table_options}</select>
|
235
273
|
<h1>#{model_name}: <%= (obj = @#{obj_name}.first).brick_descrip %></h1>
|
236
274
|
<%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
|
275
|
+
<%= form_for obj do |f| %>
|
237
276
|
<table>
|
238
277
|
<% bts = { #{bts.each_with_object([]) { |v, s| s << "#{v.first.inspect} => [#{v.last.first.inspect}, #{v.last[1].name}, #{v.last[1].primary_key.inspect}]"}.join(', ')} }
|
239
278
|
@#{obj_name}.first.attributes.each do |k, val| %>
|
240
279
|
<tr>
|
241
280
|
<% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
|
242
281
|
<th class=\"show-field\">
|
243
|
-
<% if (bt = bts[k])
|
282
|
+
<% if (bt = bts[k])
|
283
|
+
# Add a final member in this array with descriptive options to be used in <select> drop-downs
|
284
|
+
# %%% Only do this if the user has permissions to edit this bt field
|
285
|
+
bt << bt[1].order(:#{pk}).map { |obj| [obj.brick_descrip, obj.#{pk}] } if bt.length < 4 %>
|
244
286
|
BT <%= \"#\{bt.first\}-\" unless bt[1].name.underscore == bt.first.to_s %><%= bt[1].name %>
|
245
287
|
<% else %>
|
246
288
|
<%= k %>
|
247
289
|
<% end %>
|
248
290
|
</th>
|
249
291
|
<td>
|
250
|
-
<% if (bt = bts[k]) %>
|
251
|
-
<%=
|
292
|
+
<% if (bt = bts[k]) # bt_obj.brick_descrip %>
|
293
|
+
<%= f.select k.to_sym, bt[3], {}, prompt: 'Select #{model_name}' %>
|
294
|
+
<%= bt_obj = bt[1].find_by(bt[2] => val); link_to('⇛', send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym)), { class: 'show-arrow' }) if bt_obj %>
|
295
|
+
<% elsif is_bcrypt?(val) %>
|
296
|
+
<%= hide_bcrypt(val) %>
|
252
297
|
<% else %>
|
253
|
-
<%=
|
298
|
+
<%= f.text_field k.to_sym %>
|
254
299
|
<% end %>
|
255
300
|
</td>
|
256
301
|
</tr>
|
257
302
|
<% end %>
|
258
303
|
</table>
|
304
|
+
<% end %>
|
259
305
|
|
260
306
|
#{hms_headers.map do |hm|
|
261
307
|
next unless (pk = hm.first.klass.primary_key)
|
262
308
|
"<table id=\"#{hm_name = hm.first.name.to_s}\">
|
263
309
|
<tr><th>#{hm.last}</th></tr>
|
264
|
-
<%
|
310
|
+
<% collection = @#{obj_name}.first.#{hm_name}
|
311
|
+
collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection]
|
312
|
+
if collection.empty? %>
|
265
313
|
<tr><td>(none)</td></tr>
|
266
314
|
<% else %>
|
267
|
-
<% collection.
|
268
|
-
<tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{
|
315
|
+
<% collection.uniq.each do |#{hm_singular_name = hm_name.singularize.underscore}| %>
|
316
|
+
<tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore}_path(#{hm_singular_name}.#{pk})) %></td></tr>
|
269
317
|
<% end %>
|
270
318
|
<% end %>
|
271
319
|
</table>" end.join}
|
@@ -285,33 +333,14 @@ function changeout(href, param, value) {
|
|
285
333
|
|
286
334
|
if ::Brick.enable_routes? || (ENV['RAILS_ENV'] || ENV['RACK_ENV']) == 'development'
|
287
335
|
ActionDispatch::Routing::RouteSet.class_exec do
|
288
|
-
|
289
|
-
|
290
|
-
unless @finalized
|
291
|
-
existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
|
292
|
-
::Rails.application.routes.append do
|
293
|
-
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
294
|
-
# If auto-controllers and auto-models are both enabled then this makes sense:
|
295
|
-
::Brick.relations.each do |k, v|
|
296
|
-
unless existing_controllers.key?(controller_name = k.underscore.pluralize)
|
297
|
-
options = {}
|
298
|
-
options[:only] = [:index, :show] if v.key?(:isView)
|
299
|
-
send(:resources, controller_name.to_sym, **options)
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
304
|
-
_brick_finalize_routeset!(*args, **options)
|
305
|
-
end
|
336
|
+
# In order to defer auto-creation of any routes that already exist, calculate Brick routes only after having loaded all others
|
337
|
+
prepend ::Brick::RouteSet
|
306
338
|
end
|
307
339
|
end
|
308
340
|
|
309
|
-
#
|
310
|
-
|
311
|
-
|
312
|
-
::Brick._add_bt_and_hm(fk[0..2])
|
313
|
-
end
|
314
|
-
end
|
341
|
+
# Just in case it hadn't been done previously when we tried to load the brick initialiser,
|
342
|
+
# go make sure we've loaded additional references (virtual foreign keys).
|
343
|
+
::Brick.load_additional_references
|
315
344
|
|
316
345
|
# Find associative tables that can be set up for has_many :through
|
317
346
|
::Brick.relations.each do |_key, tbl|
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -174,6 +174,11 @@ module Brick
|
|
174
174
|
Brick.config.exclude_tables = value
|
175
175
|
end
|
176
176
|
|
177
|
+
# @api public
|
178
|
+
def models_inherit_from=(value)
|
179
|
+
Brick.config.models_inherit_from = value
|
180
|
+
end
|
181
|
+
|
177
182
|
# @api public
|
178
183
|
def table_name_prefixes=(value)
|
179
184
|
Brick.config.table_name_prefixes = value
|
@@ -184,6 +189,11 @@ module Brick
|
|
184
189
|
Brick.config.metadata_columns = value
|
185
190
|
end
|
186
191
|
|
192
|
+
# @api public
|
193
|
+
def not_nullables=(value)
|
194
|
+
Brick.config.not_nullables = value
|
195
|
+
end
|
196
|
+
|
187
197
|
# Additional table associations to use (Think of these as virtual foreign keys perhaps)
|
188
198
|
# @api public
|
189
199
|
def additional_references=(ars)
|
@@ -198,12 +208,12 @@ module Brick
|
|
198
208
|
# Skip creating a has_many association for these
|
199
209
|
# (Uses the same exact three-part format as would define an additional_reference)
|
200
210
|
# @api public
|
201
|
-
def
|
211
|
+
def exclude_hms=(skips)
|
202
212
|
if skips
|
203
213
|
skips = skips.call if skips.is_a?(Proc)
|
204
214
|
skips = skips.to_a unless skips.is_a?(Array)
|
205
215
|
skips = [skips] unless skips.empty? || skips.first.is_a?(Array)
|
206
|
-
Brick.config.
|
216
|
+
Brick.config.exclude_hms = skips
|
207
217
|
end
|
208
218
|
end
|
209
219
|
|
@@ -234,6 +244,18 @@ module Brick
|
|
234
244
|
Brick.config.sti_namespace_prefixes = snp
|
235
245
|
end
|
236
246
|
|
247
|
+
# Load additional references (virtual foreign keys)
|
248
|
+
# This is attempted early if a brick initialiser file is found, and then again as a failsafe at the end of our engine's initialisation
|
249
|
+
# %%% Maybe look for differences the second time 'round and just add new stuff instead of entirely deferring
|
250
|
+
def load_additional_references
|
251
|
+
return if @_additional_references_loaded
|
252
|
+
|
253
|
+
if (ars = ::Brick.config.additional_references)
|
254
|
+
ars.each { |fk| ::Brick._add_bt_and_hm(fk[0..2]) }
|
255
|
+
@_additional_references_loaded = true
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
237
259
|
|
238
260
|
# Returns Brick's `::Gem::Version`, convenient for comparisons. This is
|
239
261
|
# recommended over `::Brick::VERSION::STRING`.
|
@@ -269,6 +291,25 @@ module Brick
|
|
269
291
|
VERSION::STRING
|
270
292
|
end
|
271
293
|
end
|
294
|
+
|
295
|
+
module RouteSet
|
296
|
+
def finalize!
|
297
|
+
existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
|
298
|
+
::Rails.application.routes.append do
|
299
|
+
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
300
|
+
# If auto-controllers and auto-models are both enabled then this makes sense:
|
301
|
+
::Brick.relations.each do |k, v|
|
302
|
+
unless existing_controllers.key?(controller_name = k.underscore.pluralize)
|
303
|
+
options = {}
|
304
|
+
options[:only] = [:index, :show] if v.key?(:isView)
|
305
|
+
send(:resources, controller_name.to_sym, **options)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
super
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
272
313
|
end
|
273
314
|
|
274
315
|
require 'brick/version_number'
|
@@ -94,6 +94,9 @@ module Brick
|
|
94
94
|
# # Any tables or views you'd like to skip when auto-creating models
|
95
95
|
# Brick.exclude_tables = ['custom_metadata', 'version_info']
|
96
96
|
|
97
|
+
# # Class that auto-generated models should inherit from
|
98
|
+
# Brick.models_inherit_from = ApplicationRecord
|
99
|
+
|
97
100
|
# # When table names have specific prefixes automatically place them in their own module with a table_name_prefix.
|
98
101
|
# Brick.table_name_prefixes = { 'nav_' => 'Navigation' }
|
99
102
|
|
@@ -111,7 +114,7 @@ module Brick
|
|
111
114
|
# # Skip creating a has_many association for these
|
112
115
|
# # (Uses the same exact three-part format as would define an additional_reference)
|
113
116
|
# # Say for instance that we didn't care to display the favourite colours that users have:
|
114
|
-
# Brick.
|
117
|
+
# Brick.exclude_hms = [['users', 'favourite_colour_id', 'colours']]
|
115
118
|
|
116
119
|
# # By default primary tables involved in a foreign key relationship will indicate a \"has_many\" relationship pointing
|
117
120
|
# # back to the foreign table. In order to represent a \"has_one\" association instead, an override can be provided
|
@@ -128,6 +131,10 @@ module Brick
|
|
128
131
|
# # database:
|
129
132
|
# Brick.metadata_columns = ['last_update']
|
130
133
|
|
134
|
+
# # Columns for which to add a validate presence: true even though the database doesn't have them marked as NOT NULL.
|
135
|
+
# # Designated by <table name>.<column name>
|
136
|
+
# Brick.not_nullables = ['users.name']
|
137
|
+
|
131
138
|
# # A simple DSL is available to allow more user-friendly display of objects. Normally a user object might be shown
|
132
139
|
# # as its first non-metadata column, or if that is not available then something like \"User #45\" where 45 is that
|
133
140
|
# # object's ID. If there is no primary key then even that is not possible, so the object's .to_s method is called.
|
@@ -135,10 +142,15 @@ module Brick
|
|
135
142
|
# # user, then you can use model_descrips like this, putting expressions with property references in square brackets:
|
136
143
|
# Brick.model_descrips = { 'User' => '[profile.firstname] [profile.lastname]' }
|
137
144
|
|
138
|
-
# #
|
139
|
-
# #
|
140
|
-
# #
|
141
|
-
#
|
145
|
+
# # Specify STI subclasses either directly by name or as a general module prefix that should always relate to a specific
|
146
|
+
# # parent STI class. The prefixed :: here for these examples is mandatory. Also having a suffixed :: means instead of
|
147
|
+
# # a class reference, this is for a general namespace reference. So in this case requests for, say, either of the
|
148
|
+
# # non-existant classes Animals::Cat or Animals::Goat (or anything else with the module prefix of \"Animals::\" would
|
149
|
+
# # build a model that inherits from Animal. And a request specifically for the class Snake would build a new model
|
150
|
+
# # that inherits from Reptile, and no other request would do this -- only specifically for Snake. The ending ::
|
151
|
+
# # indicates that it's a module prefix instead of a specific class name.
|
152
|
+
# Brick.sti_namespace_prefixes = { '::Animals::' => 'Animal',
|
153
|
+
# '::Snake' => 'Reptile' }
|
142
154
|
|
143
155
|
# # If a default route is not supplied, Brick attempts to find the most \"central\" table and wires up the default
|
144
156
|
# # route to go to the :index action for what would be a controller for that table. You can specify any controller
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-03-
|
11
|
+
date: 2022-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|