brick 1.0.12 → 1.0.15

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7c3e4032e5e9f111839b7ff6e6378e4937edeabeb0edcf480cf6c50e79e599a
4
- data.tar.gz: 1a1e5b096a3f6fff0f2c49c6be6abcfbe012f3e24fccf0ea6b307b58d9cbc076
3
+ metadata.gz: 2dbd4fef7ab5c2796e75a0bce86316be00ebceb02aa5a0b8053f75b27d433c57
4
+ data.tar.gz: daa2802b2fec6533ccfed8fa9178129c6633f92b83fb092fefa48a657cb36e9b
5
5
  SHA512:
6
- metadata.gz: 4dbd885a2c4dc9666378a69423179ca2085c89b6b6120d21ef8d3274f229a88306f54868b8a18aad8e55a37a71bc9b78dff073263bb86fd89d5b982acf8d5210
7
- data.tar.gz: 952f94f89445170f26fad10357ede0df7c9053d034ddb552b1e9cba884cbdf0325325cb29a2248fac4e9de3bd8c41619fc9612d44435e6ccb808a16fd018d8dc
6
+ metadata.gz: 33f97461748561fb21900ccb1b4b39c13803e68e04fea807851b467587907577a10bd5b5a2470cde5f50bca019d3cc986b512545bf700477028f68751bb95e6a
7
+ data.tar.gz: 97605afbe32e2509800f510ca2984651cc196c048a6267fa31d65363815bc6f4484955aacd45fae1553fe70453f1bee283766812a2f92678bc35e516ec594738
data/lib/brick/config.rb CHANGED
@@ -91,6 +91,14 @@ module Brick
91
91
  @mutex.synchronize { @model_descrips = descrips }
92
92
  end
93
93
 
94
+ def sti_namespace_prefixes
95
+ @mutex.synchronize { @sti_namespace_prefixes ||= {} }
96
+ end
97
+
98
+ def sti_namespace_prefixes=(prefixes)
99
+ @mutex.synchronize { @sti_namespace_prefixes = prefixes }
100
+ end
101
+
94
102
  def skip_database_views
95
103
  @mutex.synchronize { @skip_database_views }
96
104
  end
@@ -107,6 +115,14 @@ module Brick
107
115
  @mutex.synchronize { @exclude_tables = value }
108
116
  end
109
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
+
110
126
  def table_name_prefixes
111
127
  @mutex.synchronize { @table_name_prefixes }
112
128
  end
@@ -122,5 +138,13 @@ module Brick
122
138
  def metadata_columns=(columns)
123
139
  @mutex.synchronize { @metadata_columns = columns }
124
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
125
149
  end
126
150
  end
@@ -136,38 +136,76 @@ module ActiveRecord
136
136
 
137
137
  alias _brick_find_sti_class find_sti_class
138
138
  def find_sti_class(type_name)
139
- ::Brick.sti_models[type_name] = { base: self } unless type_name.blank?
140
- module_prefixes = type_name.split('::')
141
- module_prefixes.unshift('') unless module_prefixes.first.blank?
142
- candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
143
- if File.exists?(candidate_file)
144
- # Find this STI class normally
139
+ if ::Brick.sti_models.key?(type_name)
140
+ # puts ['X', self.name, type_name].inspect
145
141
  _brick_find_sti_class(type_name)
146
142
  else
147
- # Build missing prefix modules if they don't yet exist
148
- this_module = Object
149
- module_prefixes[1..-2].each do |module_name|
150
- mod = if this_module.const_defined?(module_name)
151
- this_module.const_get(module_name)
152
- else
153
- this_module.const_set(module_name.to_sym, Module.new)
154
- end
143
+ # This auto-STI is more of a brute-force approach, building modules where needed
144
+ # The more graceful alternative is the overload of ActiveSupport::Dependencies#autoload_module! found below
145
+ ::Brick.sti_models[type_name] = { base: self } unless type_name.blank?
146
+ module_prefixes = type_name.split('::')
147
+ module_prefixes.unshift('') unless module_prefixes.first.blank?
148
+ module_name = module_prefixes[0..-2].join('::')
149
+ if ::Brick.config.sti_namespace_prefixes&.key?("::#{module_name}::") ||
150
+ ::Brick.config.sti_namespace_prefixes&.key?("#{module_name}::")
151
+ _brick_find_sti_class(type_name)
152
+ elsif File.exists?(candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
153
+ _brick_find_sti_class(type_name) # Find this STI class normally
154
+ else
155
+ # Build missing prefix modules if they don't yet exist
156
+ this_module = Object
157
+ module_prefixes[1..-2].each do |module_name|
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
171
+ end
155
172
  end
156
- # Build missing prefix modules if they don't yet exist
157
- this_module.const_set(module_prefixes.last.to_sym, klass = Class.new(self))
158
- klass
159
173
  end
160
174
  end
161
175
  end
162
176
  end
163
177
  end
164
178
 
165
- # Object.class_exec do
179
+ module ActiveSupport::Dependencies
180
+ class << self
181
+ # %%% Probably a little more targeted than other approaches we've taken thusfar
182
+ # This happens before the whole parent check
183
+ alias _brick_autoload_module! autoload_module!
184
+ def autoload_module!(*args)
185
+ into, const_name, qualified_name, path_suffix = args
186
+ if (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)&.constantize)
187
+ ::Brick.sti_models[qualified_name] = { base: base_class }
188
+ # Build subclass and place it into the specially STI-namespaced module
189
+ into.const_set(const_name.to_sym, klass = Class.new(base_class))
190
+ # %%% used to also have: autoload_once_paths.include?(base_path) ||
191
+ autoloaded_constants << qualified_name unless autoloaded_constants.include?(qualified_name)
192
+ klass
193
+ elsif (base_class = ::Brick.config.sti_namespace_prefixes&.fetch("::#{const_name}", nil)&.constantize)
194
+ # Build subclass and place it into Object
195
+ Object.const_set(const_name.to_sym, klass = Class.new(base_class))
196
+ else
197
+ _brick_autoload_module!(*args)
198
+ end
199
+ end
200
+ end
201
+ end
202
+
166
203
  class Object
167
204
  class << self
168
205
  alias _brick_const_missing const_missing
169
206
  def const_missing(*args)
170
- return Object.const_get(args.first) if Object.const_defined?(args.first)
207
+ return self.const_get(args.first) if self.const_defined?(args.first)
208
+ return Object.const_get(args.first) if Object.const_defined?(args.first) unless self == Object
171
209
 
172
210
  class_name = args.first.to_s
173
211
  # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
@@ -175,43 +213,57 @@ class Object
175
213
  # that is, checking #qualified_name_for with: from_mod, const_name
176
214
  # If we want to support namespacing in the future, might have to utilise something like this:
177
215
  # path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
178
- # return Object._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
216
+ # return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
179
217
  # If the file really exists, go and snag it:
180
- return Object._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(class_name.underscore)
218
+ if !(is_found = ActiveSupport::Dependencies.search_for_file(class_name.underscore)) && (filepath = self.name&.split('::'))
219
+ filepath = (filepath[0..-2] + [class_name]).join('/').underscore + '.rb'
220
+ end
221
+ if is_found
222
+ return self._brick_const_missing(*args)
223
+ elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
224
+ my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
225
+ return my_const
226
+ end
181
227
 
182
228
  relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
183
- is_controllers_enabled = Rails.development? || ::Brick.enable_controllers?
229
+ is_controllers_enabled = ::Brick.enable_controllers? || (ENV['RAILS_ENV'] || ENV['RACK_ENV']) == 'development'
184
230
  result = if is_controllers_enabled && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
185
- # Otherwise now it's up to us to fill in the gaps
186
- if (model = ActiveSupport::Inflector.singularize(plural_class_name).constantize)
187
- # 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.
188
- build_controller(class_name, plural_class_name, model, relations)
189
- end
190
- elsif ::Brick.enable_models?
191
- # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
192
- # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
193
- plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
194
- singular_table_name = ActiveSupport::Inflector.underscore(model_name)
195
-
196
- # Adjust for STI if we know of a base model for the requested model name
197
- table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
198
- base_model.table_name
199
- else
200
- ActiveSupport::Inflector.pluralize(singular_table_name)
201
- end
202
-
203
- # Maybe, just maybe there's a database table that will satisfy this need
204
- if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
205
- build_model(model_name, singular_table_name, table_name, relations, matching)
206
- end
207
- end
231
+ # Otherwise now it's up to us to fill in the gaps
232
+ if (model = ActiveSupport::Inflector.singularize(plural_class_name).constantize)
233
+ # 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.
234
+ build_controller(class_name, plural_class_name, model, relations)
235
+ end
236
+ elsif ::Brick.enable_models?
237
+ # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
238
+ # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
239
+ plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
240
+ singular_table_name = ActiveSupport::Inflector.underscore(model_name)
241
+
242
+ # Adjust for STI if we know of a base model for the requested model name
243
+ table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
244
+ base_model.table_name
245
+ else
246
+ ActiveSupport::Inflector.pluralize(singular_table_name)
247
+ end
248
+
249
+ # Maybe, just maybe there's a database table that will satisfy this need
250
+ if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
251
+ build_model(model_name, singular_table_name, table_name, relations, matching)
252
+ end
253
+ end
208
254
  if result
209
255
  built_class, code = result
210
256
  puts "\n#{code}"
211
257
  built_class
258
+ elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}")
259
+ # module_prefixes = type_name.split('::')
260
+ # path = self.name.split('::')[0..-2] + []
261
+ # module_prefixes.unshift('') unless module_prefixes.first.blank?
262
+ # candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
263
+ self._brick_const_missing(*args)
212
264
  else
213
- puts "MISSING! #{args.inspect} #{table_name}"
214
- Object._brick_const_missing(*args)
265
+ puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
266
+ self._brick_const_missing(*args)
215
267
  end
216
268
  end
217
269
 
@@ -223,12 +275,15 @@ class Object
223
275
 
224
276
  # Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
225
277
  if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
226
- raise NameError.new("Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\".")
278
+ unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.titleize}::")
279
+ puts "Warning: Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\"."
280
+ end
281
+ return
227
282
  end
228
283
  if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
229
284
  is_sti = true
230
285
  else
231
- base_model = ActiveRecord::Base
286
+ base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
232
287
  end
233
288
  code = +"class #{model_name} < #{base_model.name}\n"
234
289
  built_model = Class.new(base_model) do |new_model_class|
@@ -277,6 +332,12 @@ class Object
277
332
  options = {}
278
333
  singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
279
334
  macro = if assoc[:is_bt]
335
+ # Try to take care of screwy names if this is a belongs_to going to an STI subclass
336
+ # binding.pry if assoc[:fk] == 'issue_severity_id'
337
+ if (primary_class = assoc.fetch(:primary_class, nil)) &&
338
+ (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 })
339
+ assoc_name = sti_inverse_assoc.options[:inverse_of].to_s || assoc_name
340
+ end
280
341
  need_class_name = singular_table_name.underscore != assoc_name
281
342
  need_fk = "#{assoc_name}_id" != assoc[:fk]
282
343
  if (inverse = assoc[:inverse])
@@ -311,7 +372,7 @@ class Object
311
372
  end
312
373
  # Figure out if we need to specially call out the class_name and/or foreign key
313
374
  # (and if either of those then definitely also a specific inverse_of)
314
- options[:class_name] = singular_table_name.camelize if need_class_name
375
+ options[:class_name] = assoc[:primary_class]&.name || singular_table_name.camelize if need_class_name
315
376
  # Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
316
377
  if need_fk # Funky foreign key?
317
378
  options[:foreign_key] = if assoc[:fk].is_a?(Array)
@@ -356,6 +417,14 @@ class Object
356
417
  self.send(:has_many, this_hmt_fk.to_sym, **options)
357
418
  end
358
419
  end
420
+ # Not NULLables
421
+ relation[:cols].each do |col, datatype|
422
+ if (datatype[3] && ar_pks.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
423
+ ::Brick.config.not_nullables.include?("#{matching}.#{col}")
424
+ code << " validates :#{col}, presence: true\n"
425
+ self.send(:validates, col.to_sym, { presence: true })
426
+ end
427
+ end
359
428
  end
360
429
  code << "end # model #{model_name}\n\n"
361
430
  end # class definition
@@ -451,7 +520,8 @@ module ActiveRecord::ConnectionHandling
451
520
  "SELECT t.table_name AS relation_name, t.table_type,
452
521
  c.column_name, c.data_type,
453
522
  COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
454
- tc.constraint_type AS const, kcu.constraint_name AS \"key\"
523
+ tc.constraint_type AS const, kcu.constraint_name AS \"key\",
524
+ c.is_nullable
455
525
  FROM INFORMATION_SCHEMA.tables AS t
456
526
  LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
457
527
  AND t.table_name = c.table_name
@@ -489,7 +559,7 @@ module ActiveRecord::ConnectionHandling
489
559
  end
490
560
  key << col_name if key
491
561
  cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
492
- cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name)]
562
+ cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
493
563
  # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
494
564
  end
495
565
  else # MySQL2 acts a little differently, bringing back an array for each row
@@ -593,16 +663,17 @@ module Brick
593
663
  bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
594
664
 
595
665
  bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
596
- hms = (relation = relations.fetch(fk[2], nil))&.fetch(:fks) { relation[:fks] = {} }
666
+ primary_table = (is_class = fk[2].is_a?(Hash) && fk[2].key?(:class)) ? (primary_class = fk[2][:class].constantize).table_name : fk[2]
667
+ hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
597
668
 
598
669
  unless (cnstr_name = fk[3])
599
670
  # For any appended references (those that come from config), arrive upon a definitely unique constraint name
600
- cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{fk[2]}"
671
+ cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{is_class ? fk[2][:class].underscore : fk[2]}"
601
672
  cnstr_added_num = 1
602
673
  cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
603
674
  missing = []
604
675
  missing << fk[0] unless relations.key?(fk[0])
605
- missing << fk[2] unless relations.key?(fk[2])
676
+ missing << primary_table unless is_class || relations.key?(primary_table)
606
677
  unless missing.empty?
607
678
  tables = relations.reject { |k, v| v.fetch(:isView, nil) }.keys.sort
608
679
  puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
@@ -613,8 +684,12 @@ module Brick
613
684
  puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
614
685
  return
615
686
  end
616
- if (redundant = bts.find { |k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == fk[2] })
617
- puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed. (Already established by #{redundant.first}.)"
687
+ if (redundant = bts.find { |k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == primary_table })
688
+ if is_class && !redundant.last.key?(:class)
689
+ 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
690
+ else
691
+ puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed. (Already established by #{redundant.first}.)"
692
+ end
618
693
  return
619
694
  end
620
695
  end
@@ -622,10 +697,16 @@ module Brick
622
697
  assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[1]] : assoc_bt[:fk].concat(fk[1])
623
698
  assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
624
699
  else
625
- assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: fk[2] }
700
+ assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: primary_table }
701
+ end
702
+ if is_class
703
+ # For use in finding the proper :source for a HMT association that references an STI subclass
704
+ assoc_bt[:primary_class] = primary_class
705
+ # For use in finding the proper :inverse_of for a BT association that references an STI subclass
706
+ # assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] }
626
707
  end
627
708
 
628
- unless ::Brick.config.skip_hms&.any? { |skip| fk[0] == skip[0] && fk[1] == skip[1] && fk[2] == skip[2] }
709
+ unless is_class || ::Brick.config.skip_hms&.any? { |skip| fk[0] == skip[0] && fk[1] == skip[1] && primary_table == skip[2] }
629
710
  cnstr_name = "hm_#{cnstr_name}"
630
711
  if (assoc_hm = hms.fetch(cnstr_name, nil))
631
712
  assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
@@ -640,15 +721,5 @@ module Brick
640
721
  end
641
722
  # hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
642
723
  end
643
-
644
- # Rails < 4.0 doesn't have ActiveRecord::RecordNotUnique, so use the more generic ActiveRecord::ActiveRecordError instead
645
- ar_not_unique_error = ActiveRecord.const_defined?('RecordNotUnique') ? ActiveRecord::RecordNotUnique : ActiveRecord::ActiveRecordError
646
- class NoUniqueColumnError < ar_not_unique_error
647
- end
648
-
649
- # Rails < 4.2 doesn't have ActiveRecord::RecordInvalid, so use the more generic ActiveRecord::ActiveRecordError instead
650
- ar_invalid_error = ActiveRecord.const_defined?('RecordInvalid') ? ActiveRecord::RecordInvalid : ActiveRecord::ActiveRecordError
651
- class LessThanHalfAreMatchingColumnsError < ar_invalid_error
652
- end
653
724
  end
654
725
  end
@@ -16,12 +16,18 @@ 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
 
@@ -37,7 +43,7 @@ module Brick
37
43
  # ====================================
38
44
  # Dynamically create generic templates
39
45
  # ====================================
40
- if Rails.development? || ::Brick.enable_views?
46
+ if ::Brick.enable_views? || (ENV['RAILS_ENV'] || ENV['RACK_ENV']) == 'development'
41
47
  ActionView::LookupContext.class_exec do
42
48
  alias :_brick_template_exists? :template_exists?
43
49
  def template_exists?(*args, **options)
@@ -283,26 +289,10 @@ function changeout(href, param, value) {
283
289
  end
284
290
  end
285
291
 
286
- if Rails.development? || ::Brick.enable_routes?
292
+ if ::Brick.enable_routes? || (ENV['RAILS_ENV'] || ENV['RACK_ENV']) == 'development'
287
293
  ActionDispatch::Routing::RouteSet.class_exec do
288
- alias _brick_finalize_routeset! finalize!
289
- def finalize!(*args, **options)
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
294
+ # In order to defer auto-creation of any routes that already exist, calculate Brick routes only after having loaded all others
295
+ prepend ::Brick::RouteSet
306
296
  end
307
297
  end
308
298
 
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 12
8
+ TINY = 15
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
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)
@@ -228,6 +238,13 @@ module Brick
228
238
  Brick.config.model_descrips = descrips
229
239
  end
230
240
 
241
+ # Module prefixes to build out and associate with specific base STI models
242
+ # @api public
243
+ def sti_namespace_prefixes=(snp)
244
+ Brick.config.sti_namespace_prefixes = snp
245
+ end
246
+
247
+
231
248
  # Returns Brick's `::Gem::Version`, convenient for comparisons. This is
232
249
  # recommended over `::Brick::VERSION::STRING`.
233
250
  #
@@ -262,6 +279,25 @@ module Brick
262
279
  VERSION::STRING
263
280
  end
264
281
  end
282
+
283
+ module RouteSet
284
+ def finalize!
285
+ existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
286
+ ::Rails.application.routes.append do
287
+ # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
288
+ # If auto-controllers and auto-models are both enabled then this makes sense:
289
+ ::Brick.relations.each do |k, v|
290
+ unless existing_controllers.key?(controller_name = k.underscore.pluralize)
291
+ options = {}
292
+ options[:only] = [:index, :show] if v.key?(:isView)
293
+ send(:resources, controller_name.to_sym, **options)
294
+ end
295
+ end
296
+ end
297
+ super
298
+ end
299
+ end
300
+
265
301
  end
266
302
 
267
303
  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 for auto-generated models to 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
 
@@ -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,6 +142,16 @@ 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
 
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' }
154
+
138
155
  # # If a default route is not supplied, Brick attempts to find the most \"central\" table and wires up the default
139
156
  # # route to go to the :index action for what would be a controller for that table. You can specify any controller
140
157
  # # name and action you wish in order to override this and have that be the default route when none other has been
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.12
4
+ version: 1.0.15
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-23 00:00:00.000000000 Z
11
+ date: 2022-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord