brick 1.0.6 → 1.0.10

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: 634669de2074746d62642b8c9108070b78e92b14f7924ac969404b240de2267d
4
- data.tar.gz: 7614fa456fcaeb6bc68227230088fe3adeed31278241a000f3fb19792b763d26
3
+ metadata.gz: cfc0c2e81700a407de1fe154d337dd83d756a29fba0914db5eeb1974198720d2
4
+ data.tar.gz: 0051e6b4fe55d1e9f4cc4614aabd941a6947f66af5ac41f35725df594217fd2d
5
5
  SHA512:
6
- metadata.gz: 3b89ed9c8ffe3c63e098a3f7a8e5a9987db0c00bfe2c917b7c2acefebc30653a4a88d091852b8f8e6128daa310e7c7742d10611acfb0ffe7d06cc6c01da7068e
7
- data.tar.gz: 98d58b4461b9e85537da640e3940316553d142a341547086ce614f75d9366145c8818beb6e972ac93a77e7209b034eb64a64eb9a078f6afb91fbbd008e3ade69
6
+ metadata.gz: 445acf196541b17429bb7c5f93b64ebd51cc4c3b1fcc4cec7fcb43cca6e47ed86a027bc0010b3053a763414809a390509d9da9c296c4eab85bbd794a3553ba90
7
+ data.tar.gz: 110cd25429c21410effe398a61c4284876d49e222267987e0c5e0e09b5ab8e84bd4991fbd36257077f5178fa3fcba661970162e574744c2f8d2b80ec4a1e2d51
data/lib/brick/config.rb CHANGED
@@ -65,13 +65,30 @@ module Brick
65
65
  @mutex.synchronize { @additional_references = references }
66
66
  end
67
67
 
68
- # Associations to treat has a has_one
68
+ # Skip creating a has_many association for these
69
+ def skip_hms
70
+ @mutex.synchronize { @skip_hms }
71
+ end
72
+
73
+ def skip_hms=(skips)
74
+ @mutex.synchronize { @skip_hms = skips }
75
+ end
76
+
77
+ # Associations to treat as a has_one
69
78
  def has_ones
70
79
  @mutex.synchronize { @has_ones }
71
80
  end
72
81
 
73
- def has_ones=(references)
74
- @mutex.synchronize { @has_ones = references }
82
+ def has_ones=(hos)
83
+ @mutex.synchronize { @has_ones = hos }
84
+ end
85
+
86
+ def model_descrips
87
+ @mutex.synchronize { @model_descrips }
88
+ end
89
+
90
+ def model_descrips=(descrips)
91
+ @mutex.synchronize { @model_descrips = descrips }
75
92
  end
76
93
 
77
94
  def skip_database_views
@@ -90,6 +107,14 @@ module Brick
90
107
  @mutex.synchronize { @exclude_tables = value }
91
108
  end
92
109
 
110
+ def table_name_prefixes
111
+ @mutex.synchronize { @table_name_prefixes }
112
+ end
113
+
114
+ def table_name_prefixes=(value)
115
+ @mutex.synchronize { @table_name_prefixes = value }
116
+ end
117
+
93
118
  def metadata_columns
94
119
  @mutex.synchronize { @metadata_columns }
95
120
  end
@@ -46,7 +46,56 @@ module ActiveRecord
46
46
  # Used to show a little prettier name for an object
47
47
  def brick_descrip
48
48
  klass = self.class
49
- klass.primary_key ? "#{klass.name} ##{send(klass.primary_key)}" : to_s
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
+
53
+ # If there's no DSL yet specified, just try to find the first usable column on this model
54
+ unless ::Brick.config.model_descrips[klass.name]
55
+ descrip_col = (klass.columns.map(&:name) - klass._brick_get_fks -
56
+ (::Brick.config.metadata_columns || []) -
57
+ [klass.primary_key]).first
58
+ ::Brick.config.model_descrips[klass.name] = "[#{descrip_col}]" if descrip_col
59
+ end
60
+ if (dsl ||= ::Brick.config.model_descrips[klass.name])
61
+ caches = {}
62
+ output = +''
63
+ is_brackets_have_content = false
64
+ bracket_name = nil
65
+ dsl.each_char do |ch|
66
+ if bracket_name
67
+ if ch == ']' # Time to process a bracketed thing?
68
+ obj_name = +''
69
+ obj = self
70
+ bracket_name.split('.').each do |part|
71
+ obj_name += ".#{part}"
72
+ obj = if caches.key?(obj_name)
73
+ caches[obj_name]
74
+ else
75
+ (caches[obj_name] = obj&.send(part.to_sym))
76
+ end
77
+ end
78
+ is_brackets_have_content = true unless (obj&.to_s).blank?
79
+ output << (obj&.to_s || '')
80
+ bracket_name = nil
81
+ else
82
+ bracket_name << ch
83
+ end
84
+ elsif ch == '['
85
+ bracket_name = +''
86
+ else
87
+ output << ch
88
+ end
89
+ end
90
+ output += bracket_name if bracket_name
91
+ end
92
+ if is_brackets_have_content
93
+ output
94
+ elsif klass.primary_key
95
+ "#{klass.name} ##{send(klass.primary_key)}"
96
+ else
97
+ to_s
98
+ end
50
99
  end
51
100
 
52
101
  private
@@ -123,12 +172,13 @@ class Object
123
172
  # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
124
173
  plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
125
174
  singular_table_name = ActiveSupport::Inflector.underscore(model_name)
126
- table_name = ActiveSupport::Inflector.pluralize(singular_table_name)
127
175
 
128
176
  # Adjust for STI if we know of a base model for the requested model name
129
- if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
130
- table_name = base_model.table_name
131
- end
177
+ table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
178
+ base_model.table_name
179
+ else
180
+ ActiveSupport::Inflector.pluralize(singular_table_name)
181
+ end
132
182
 
133
183
  # Maybe, just maybe there's a database table that will satisfy this need
134
184
  if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
@@ -155,7 +205,11 @@ class Object
155
205
  if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
156
206
  raise NameError.new("Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\".")
157
207
  end
158
- base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ActiveRecord::Base
208
+ if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
209
+ is_sti = true
210
+ else
211
+ base_model = ActiveRecord::Base
212
+ end
159
213
  code = +"class #{model_name} < #{base_model.name}\n"
160
214
  built_model = Class.new(base_model) do |new_model_class|
161
215
  Object.const_set(model_name.to_sym, new_model_class)
@@ -193,60 +247,93 @@ class Object
193
247
  code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
194
248
  end
195
249
 
196
- fks = relation[:fks] || {}
197
- fks.each do |_constraint_name, assoc|
198
- assoc_name = assoc[:assoc_name]
199
- inverse_assoc_name = assoc[:inverse][:assoc_name]
200
- options = {}
201
- singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
202
- macro = if assoc[:is_bt]
203
- need_class_name = singular_table_name.underscore != assoc_name
204
- need_fk = "#{assoc_name}_id" != assoc[:fk]
205
- inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], assoc[:inverse])
206
- if (has_ones = ::Brick.config.has_ones.fetch(assoc[:inverse][:alternate_name].camelize, nil))&.key?(singular_inv_assoc_name = ActiveSupport::Inflector.singularize(inverse_assoc_name))
207
- inverse_assoc_name = if has_ones[singular_inv_assoc_name]
208
- need_inverse_of = true
209
- has_ones[singular_inv_assoc_name]
210
- else
211
- singular_inv_assoc_name
212
- end
213
- end
214
- :belongs_to
215
- else
216
- # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
217
- # Are there multiple foreign keys out to the same table?
218
- assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
219
- need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
220
- # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
221
- if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
222
- assoc_name = if has_ones[singular_assoc_name]
223
- need_class_name = true
224
- has_ones[singular_assoc_name]
225
- else
226
- singular_assoc_name
227
- end
228
- :has_one
250
+ unless is_sti
251
+ fks = relation[:fks] || {}
252
+ # Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
253
+ hmts = fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts|
254
+ # The key in each hash entry (fk.first) is the constraint name
255
+ assoc_name = (assoc = fk.last)[:assoc_name]
256
+ inverse_assoc_name = assoc[:inverse]&.fetch(:assoc_name, nil)
257
+ options = {}
258
+ singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
259
+ macro = if assoc[:is_bt]
260
+ need_class_name = singular_table_name.underscore != assoc_name
261
+ need_fk = "#{assoc_name}_id" != assoc[:fk]
262
+ if (inverse = assoc[:inverse])
263
+ inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], inverse)
264
+ if (has_ones = ::Brick.config.has_ones&.fetch(inverse[:alternate_name].camelize, nil))&.key?(singular_inv_assoc_name = ActiveSupport::Inflector.singularize(inverse_assoc_name))
265
+ inverse_assoc_name = if has_ones[singular_inv_assoc_name]
266
+ need_inverse_of = true
267
+ has_ones[singular_inv_assoc_name]
268
+ else
269
+ singular_inv_assoc_name
270
+ end
271
+ end
272
+ end
273
+ :belongs_to
229
274
  else
230
- :has_many
275
+ # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
276
+ # Are there multiple foreign keys out to the same table?
277
+ assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
278
+ need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
279
+ # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
280
+ if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
281
+ assoc_name = if has_ones[singular_assoc_name]
282
+ need_class_name = true
283
+ has_ones[singular_assoc_name]
284
+ else
285
+ singular_assoc_name
286
+ end
287
+ :has_one
288
+ else
289
+ :has_many
290
+ end
231
291
  end
232
- end
233
- # Figure out if we need to specially call out the class_name and/or foreign key
234
- # (and if either of those then definitely also a specific inverse_of)
235
- options[:class_name] = singular_table_name.camelize if need_class_name
236
- options[:foreign_key] = assoc[:fk].to_sym if need_fk # Funky foreign key?
237
- options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk || need_inverse_of
238
- assoc_name = assoc_name.to_sym
239
- code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
240
- self.send(macro, assoc_name, **options)
241
-
242
- # Look for any valid "has_many :through"
243
- if macro == :has_many
244
- relations[assoc[:inverse_table]][:hmt_fks].each do |k, hmt_fk|
245
- next if k == assoc[:fk]
246
-
247
- hmt_fk = ActiveSupport::Inflector.pluralize(hmt_fk)
248
- code << " has_many :#{hmt_fk}, through: #{assoc_name.inspect}\n"
249
- self.send(:has_many, hmt_fk.to_sym, **{ through: assoc_name })
292
+ # Figure out if we need to specially call out the class_name and/or foreign key
293
+ # (and if either of those then definitely also a specific inverse_of)
294
+ options[:class_name] = singular_table_name.camelize if need_class_name
295
+ # Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
296
+ if need_fk # Funky foreign key?
297
+ options[:foreign_key] = if assoc[:fk].is_a?(Array)
298
+ assoc_fk = assoc[:fk].uniq
299
+ assoc_fk.length < 2 ? assoc_fk.first : assoc_fk
300
+ else
301
+ assoc[:fk].to_sym
302
+ end
303
+ end
304
+ options[:inverse_of] = inverse_assoc_name.to_sym if inverse_assoc_name && (need_class_name || need_fk || need_inverse_of)
305
+
306
+ # Prepare a list of entries for "has_many :through"
307
+ if macro == :has_many
308
+ relations[assoc[:inverse_table]][:hmt_fks].each do |k, hmt_fk|
309
+ next if k == assoc[:fk]
310
+
311
+ hmts[ActiveSupport::Inflector.pluralize(hmt_fk.last)] << [assoc, hmt_fk.first]
312
+ end
313
+ end
314
+
315
+ # And finally create a has_one, has_many, or belongs_to for this association
316
+ assoc_name = assoc_name.to_sym
317
+ code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
318
+ self.send(macro, assoc_name, **options)
319
+ hmts
320
+ end
321
+ hmts.each do |hmt_fk, fks|
322
+ fks.each do |fk|
323
+ source = nil
324
+ this_hmt_fk = if fks.length > 1
325
+ singular_assoc_name = ActiveSupport::Inflector.singularize(fk.first[:inverse][:assoc_name])
326
+ source = fk.last
327
+ through = ActiveSupport::Inflector.pluralize(fk.first[:alternate_name])
328
+ "#{singular_assoc_name}_#{hmt_fk}"
329
+ else
330
+ through = fk.first[:assoc_name]
331
+ hmt_fk
332
+ end
333
+ code << " has_many :#{this_hmt_fk}, through: #{(assoc_name = through.to_sym).to_sym.inspect}#{", source: :#{source}" if source}\n"
334
+ options = { through: assoc_name }
335
+ options[:source] = source.to_sym if source
336
+ self.send(:has_many, this_hmt_fk.to_sym, **options)
250
337
  end
251
338
  end
252
339
  end
@@ -268,6 +355,7 @@ class Object
268
355
  code << " @#{table_name}.brick_where(params)\n"
269
356
  code << " end\n"
270
357
  self.define_method :index do
358
+ ::Brick.set_db_schema(params)
271
359
  ar_relation = model.primary_key ? model.order(model.primary_key) : model.all
272
360
  instance_variable_set(:@_brick_params, ar_relation.brick_where(params))
273
361
  instance_variable_set("@#{table_name}".to_sym, ar_relation)
@@ -278,6 +366,7 @@ class Object
278
366
  code << " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n"
279
367
  code << " end\n"
280
368
  self.define_method :show do
369
+ ::Brick.set_db_schema(params)
281
370
  instance_variable_set("@#{singular_table_name}".to_sym, model.find(params[:id].split(',')))
282
371
  end
283
372
  end
@@ -293,7 +382,8 @@ class Object
293
382
  end
294
383
 
295
384
  def _brick_get_hm_assoc_name(relation, hm_assoc)
296
- if relation[:hm_counts][hm_assoc[:assoc_name]] > 1
385
+ binding.pry if hm_assoc.nil?
386
+ if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
297
387
  [ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name]), true]
298
388
  else
299
389
  [ActiveSupport::Inflector.pluralize(hm_assoc[:inverse_table]), nil]
@@ -319,9 +409,11 @@ module ActiveRecord::ConnectionHandling
319
409
  # Only for Postgres? (Doesn't work in sqlite3)
320
410
  # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
321
411
 
322
- case ActiveRecord::Base.connection.adapter_name
412
+ schema_sql = 'SELECT NULL AS table_schema;'
413
+ case ActiveRecord::Base.connection.adapter_name
323
414
  when 'PostgreSQL'
324
415
  schema = 'public'
416
+ schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
325
417
  when 'Mysql2'
326
418
  schema = ActiveRecord::Base.connection.current_database
327
419
  when 'SQLite'
@@ -429,6 +521,10 @@ module ActiveRecord::ConnectionHandling
429
521
  else
430
522
  end
431
523
  if sql
524
+ ::Brick.db_schemas = ActiveRecord::Base.connection.execute(schema_sql)
525
+ ::Brick.db_schemas = ::Brick.db_schemas.to_a unless ::Brick.db_schemas.is_a?(Array)
526
+ ::Brick.db_schemas.map! { |row| row['table_schema'] } unless ::Brick.db_schemas.empty? || ::Brick.db_schemas.first.is_a?(String)
527
+ ::Brick.db_schemas -= ['information_schema', 'pg_catalog']
432
528
  ActiveRecord::Base.connection.execute(sql).each do |fk|
433
529
  fk = fk.values unless fk.is_a?(Array)
434
530
  ::Brick._add_bt_and_hm(fk, relations)
@@ -469,65 +565,69 @@ module Brick
469
565
  end # module Extensions
470
566
  # rubocop:enable Style/CommentedKeyword
471
567
 
472
- def self._add_bt_and_hm(fk, relations = nil)
473
- relations ||= ::Brick.relations
474
- bt_assoc_name = fk[1].underscore
475
- bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
476
-
477
- bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
478
- hms = (relation = relations.fetch(fk[2], nil))&.fetch(:fks) { relation[:fks] = {} }
479
-
480
- unless (cnstr_name = fk[3])
481
- # For any appended references (those that come from config), arrive upon a definitely unique constraint name
482
- cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{fk[2]}"
483
- cnstr_added_num = 1
484
- cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
485
- missing = []
486
- missing << fk[0] unless relations.key?(fk[0])
487
- missing << fk[2] unless relations.key?(fk[2])
488
- unless missing.empty?
489
- tables = relations.reject { |k, v| v.fetch(:isView, nil) }.keys.sort
490
- puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
491
- return
568
+ class << self
569
+ def _add_bt_and_hm(fk, relations = nil)
570
+ relations ||= ::Brick.relations
571
+ bt_assoc_name = fk[1].underscore
572
+ bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
573
+
574
+ bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
575
+ hms = (relation = relations.fetch(fk[2], nil))&.fetch(:fks) { relation[:fks] = {} }
576
+
577
+ unless (cnstr_name = fk[3])
578
+ # For any appended references (those that come from config), arrive upon a definitely unique constraint name
579
+ cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{fk[2]}"
580
+ cnstr_added_num = 1
581
+ cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
582
+ missing = []
583
+ missing << fk[0] unless relations.key?(fk[0])
584
+ missing << fk[2] unless relations.key?(fk[2])
585
+ unless missing.empty?
586
+ tables = relations.reject { |k, v| v.fetch(:isView, nil) }.keys.sort
587
+ puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
588
+ return
589
+ end
590
+ unless (cols = relations[fk[0]][:cols]).key?(fk[1])
591
+ columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
592
+ puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
593
+ return
594
+ end
595
+ if (redundant = bts.find { |k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == fk[2] })
596
+ puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed. (Already established by #{redundant.first}.)"
597
+ return
598
+ end
492
599
  end
493
- unless (cols = relations[fk[0]][:cols]).key?(fk[1])
494
- columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
495
- puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
496
- return
600
+ if (assoc_bt = bts[cnstr_name])
601
+ assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[1]] : assoc_bt[:fk].concat(fk[1])
602
+ assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
603
+ else
604
+ assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: fk[2] }
497
605
  end
498
- if (redundant = bts.find{|k, v| v[:inverse][:inverse_table] == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == fk[2] })
499
- puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed. (Already established by #{redundant.first}.)"
500
- return
606
+
607
+ unless ::Brick.config.skip_hms&.any? { |skip| fk[0] == skip[0] && fk[1] == skip[1] && fk[2] == skip[2] }
608
+ cnstr_name = "hm_#{cnstr_name}"
609
+ if (assoc_hm = hms.fetch(cnstr_name, nil))
610
+ assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
611
+ assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
612
+ assoc_hm[:inverse] = assoc_bt
613
+ else
614
+ 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 }
615
+ hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
616
+ hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
617
+ end
618
+ assoc_bt[:inverse] = assoc_hm
501
619
  end
620
+ # hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
502
621
  end
503
622
 
504
- if (assoc_bt = bts[cnstr_name])
505
- assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[1]] : assoc_bt[:fk].concat(fk[1])
506
- assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
507
- else
508
- assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: fk[2] }
623
+ # Rails < 4.0 doesn't have ActiveRecord::RecordNotUnique, so use the more generic ActiveRecord::ActiveRecordError instead
624
+ ar_not_unique_error = ActiveRecord.const_defined?('RecordNotUnique') ? ActiveRecord::RecordNotUnique : ActiveRecord::ActiveRecordError
625
+ class NoUniqueColumnError < ar_not_unique_error
509
626
  end
510
627
 
511
- if (assoc_hm = hms[cnstr_name])
512
- assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
513
- assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
514
- assoc_hm[:inverse] = assoc_bt
515
- else
516
- 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 }
517
- hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
518
- hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
628
+ # Rails < 4.2 doesn't have ActiveRecord::RecordInvalid, so use the more generic ActiveRecord::ActiveRecordError instead
629
+ ar_invalid_error = ActiveRecord.const_defined?('RecordInvalid') ? ActiveRecord::RecordInvalid : ActiveRecord::ActiveRecordError
630
+ class LessThanHalfAreMatchingColumnsError < ar_invalid_error
519
631
  end
520
- assoc_bt[:inverse] = assoc_hm
521
- # hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
522
- end
523
-
524
- # Rails < 4.0 doesn't have ActiveRecord::RecordNotUnique, so use the more generic ActiveRecord::ActiveRecordError instead
525
- ar_not_unique_error = ActiveRecord.const_defined?('RecordNotUnique') ? ActiveRecord::RecordNotUnique : ActiveRecord::ActiveRecordError
526
- class NoUniqueColumnError < ar_not_unique_error
527
- end
528
-
529
- # Rails < 4.2 doesn't have ActiveRecord::RecordInvalid, so use the more generic ActiveRecord::ActiveRecordError instead
530
- ar_invalid_error = ActiveRecord.const_defined?('RecordInvalid') ? ActiveRecord::RecordInvalid : ActiveRecord::ActiveRecordError
531
- class LessThanHalfAreMatchingColumnsError < ar_invalid_error
532
632
  end
533
633
  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
+ # When table names have specific prefixes, automatically place them in their own module with a table_name_prefix.
20
+ ::Brick.table_name_prefixes = app.config.brick.fetch(:table_name_prefixes, [])
21
+
19
22
  # Columns to treat as being metadata for purposes of identifying associative tables for has_many :through
20
23
  ::Brick.metadata_columns = app.config.brick.fetch(:metadata_columns, ['created_at', 'updated_at', 'deleted_at'])
21
24
 
22
25
  # Additional references (virtual foreign keys)
23
26
  ::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)
24
27
 
28
+ # Skip creating a has_many association for these
29
+ ::Brick.skip_hms = app.config.brick.fetch(:skip_hms, nil)
30
+
25
31
  # Has one relationships
26
32
  ::Brick.has_ones = app.config.brick.fetch(:has_ones, nil)
27
33
  end
@@ -62,78 +68,148 @@ module Brick
62
68
  # This gets has_many as well as has_many :through
63
69
  # %%% weed out ones that don't have an available model to reference
64
70
  bts, hms = ::Brick.get_bts_and_hms(@_brick_model)
65
- # Weed out has_manys that go to an associative table
66
- associatives = hms.select { |k, v| v.options[:through] }.each_with_object({}) do |hmt, s|
67
- s[hmt.first] = hms.delete(hmt.last.options[:through]) # End up with a hash of HMT names pointing to join-table associations
68
- end
69
- hms_headers = hms.each_with_object(+'') { |hm, s| s << "<th>H#{hm.last.macro == :has_one ? 'O' : 'M'}#{'T' if hm.last.options[:through]} #{hm.first}</th>\n" }
70
- hms_columns = hms.each_with_object(+'') do |hm, s|
71
- hm_fk_name = if hm.last.options[:through]
72
- associative = associatives[hm.last.name]
73
- "'#{associative.name}.#{associative.foreign_key}'"
74
- else
75
- hm.last.foreign_key
71
+ # Mark has_manys that go to an associative ("join") table so that they are skipped in the UI,
72
+ # as well as any possible polymorphic associations
73
+ skip_hms = {}
74
+ associatives = hms.each_with_object({}) do |hmt, s|
75
+ if (through = hmt.last.options[:through])
76
+ skip_hms[through] = nil
77
+ s[hmt.first] = hms[through] # End up with a hash of HMT names pointing to join-table associations
78
+ elsif hmt.last.inverse_of.nil?
79
+ puts "SKIPPING #{hmt.last.name.inspect}"
80
+ # %%% If we don't do this then below associative.name will find that associative is nil
81
+ skip_hms[hmt.last.name] = nil
76
82
  end
77
- s << if hm.last.macro == :has_many
83
+ end
84
+
85
+ schema_options = ::Brick.db_schemas.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
86
+ hms_columns = +'' # Used for 'index'
87
+ # puts skip_hms.inspect
88
+ hms_headers = hms.each_with_object([]) do |hm, s|
89
+ next if skip_hms.key?(hm.last.name)
90
+
91
+ if args.first == 'index'
92
+ hm_fk_name = if hm.last.options[:through]
93
+ associative = associatives[hm.last.name]
94
+ "'#{associative.name}.#{associative.foreign_key}'"
95
+ else
96
+ hm.last.foreign_key
97
+ end
98
+ hms_columns << if hm.last.macro == :has_many
78
99
  "<td>
79
100
  <%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm.last.klass.name.underscore.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) unless #{obj_name}.#{hm.first}.count.zero? %>
80
101
  </td>\n"
81
- else # has_one
102
+ else # has_one
82
103
  "<td>
83
104
  <%= obj = #{obj_name}.#{hm.first}; link_to(obj.brick_descrip, obj) if obj %>
84
105
  </td>\n"
85
- end
106
+ end
107
+ end
108
+ s << [hm.last, "H#{hm.last.macro == :has_one ? 'O' : 'M'}#{'T' if hm.last.options[:through]} #{hm.first}"]
86
109
  end
87
110
 
111
+ css = "<style>
112
+ table {
113
+ border-collapse: collapse;
114
+ margin: 25px 0;
115
+ font-size: 0.9em;
116
+ font-family: sans-serif;
117
+ min-width: 400px;
118
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
119
+ }
120
+
121
+ table thead tr th, table tr th {
122
+ background-color: #009879;
123
+ color: #ffffff;
124
+ text-align: left;
125
+ }
126
+
127
+ table th, table td {
128
+ padding: 0.2em 0.5em;
129
+ }
130
+
131
+ .show-field {
132
+ background-color: #004998;
133
+ }
134
+
135
+ table tbody tr {
136
+ border-bottom: thin solid #dddddd;
137
+ }
138
+
139
+ table tbody tr:nth-of-type(even) {
140
+ background-color: #f3f3f3;
141
+ }
142
+
143
+ table tbody tr:last-of-type {
144
+ border-bottom: 2px solid #009879;
145
+ }
146
+
147
+ table tbody tr.active-row {
148
+ font-weight: bold;
149
+ color: #009879;
150
+ }
151
+
152
+ a.show-arrow {
153
+ font-size: 2.5em;
154
+ text-decoration: none;
155
+ }
156
+ </style>"
157
+
158
+ script = "<script>
159
+ var schemaSelect = document.getElementById(\"schema\");
160
+ if (schemaSelect) {
161
+ var brickSchema = changeout(location.href, \"_brick_schema\");
162
+ if (brickSchema) {
163
+ [... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
164
+ }
165
+ schemaSelect.value = brickSchema || \"public\";
166
+ schemaSelect.focus();
167
+ schemaSelect.addEventListener(\"change\", function () {
168
+ location.href = changeout(location.href, \"_brick_schema\", this.value);
169
+ });
170
+ }
171
+ function changeout(href, param, value) {
172
+ var hrefParts = href.split(\"?\");
173
+ var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
174
+ params = params.reduce(function (s, v) { var parts = v.split(\"=\"); s[parts[0]] = parts[1]; return s; }, {});
175
+ if (value === undefined) return params[param];
176
+ params[param] = value;
177
+ return hrefParts[0] + \"?\" + Object.keys(params).reduce(function (s, v) { s.push(v + \"=\" + params[v]); return s; }, []).join(\"&\");
178
+ }
179
+ </script>"
180
+
88
181
  inline = case args.first
89
182
  when 'index'
90
- "<p style=\"color: green\"><%= notice %></p>
91
-
183
+ "#{css}
184
+ <p style=\"color: green\"><%= notice %></p>#{"
185
+ <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
92
186
  <h1>#{model_name.pluralize}</h1>
93
187
  <% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
94
-
95
188
  <table id=\"#{table_name}\">
96
- <tr>
97
- <% is_first = true; is_need_id_col = nil
98
- 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(', ')} }
189
+ <thead><tr>#{"<th></th>" if pk }
190
+ <% 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(', ')} }
99
191
  @#{table_name}.columns.map(&:name).each do |col| %>
100
192
  <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) %>
101
193
  <th>
102
- <% if bt = bts[col]
103
- if is_first
104
- is_first = false
105
- is_need_id_col = true %>
106
- </th><th>
107
- <% end %>
194
+ <% if (bt = bts[col]) %>
108
195
  BT <%= \"#\{bt.first\}-\" unless bt[1].name.underscore == bt.first.to_s %><%= bt[1].name %>
109
- <% else
110
- is_first = false %>
196
+ <% else %>
111
197
  <%= col %>
112
198
  <% end %>
113
199
  </th>
114
200
  <% end %>
115
- <% if is_first # STILL haven't been able to write a first non-key / non-metadata column?
116
- is_first = false
117
- is_need_id_col = true %>
118
- <th></th>
119
- <% end %>
120
- #{hms_headers}
121
- </tr>
201
+ #{hms_headers.map { |h| "<th>#{h.last}</th>\n" }.join}
202
+ </tr></thead>
122
203
 
204
+ <tbody>
123
205
  <% @#{table_name}.each do |#{obj_name}| %>
124
- <tr>
125
- <% is_first = true
126
- if is_need_id_col
127
- is_first = false %>
128
- <td><%= link_to \"#\{#{obj_name}.class.name\} ##\{#{obj_name}.id\}\", #{obj_name} %></td>
129
- <% end %>
206
+ <tr>#{"
207
+ <td><%= link_to '⇛', #{obj_name}_path(#{obj_name}.#{pk}), { class: 'show-arrow' } %></td>" if pk }
130
208
  <% #{obj_name}.attributes.each do |k, val| %>
131
209
  <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
132
210
  <td>
133
211
  <% if (bt = bts[k]) %>
134
- <%= obj = bt[1].find_by(bt.last => val); link_to(obj.brick_descrip, obj) if obj %>
135
- <% elsif is_first %>
136
- <%= is_first = false; link_to val, #{obj_name}_path(#{obj_name}.#{pk}) %>
212
+ <%= bt_obj = bt[1].find_by(bt.last => val); link_to(bt_obj.brick_descrip, bt_obj) if bt_obj %>
137
213
  <% else %>
138
214
  <%= val %>
139
215
  <% end %>
@@ -142,13 +218,55 @@ module Brick
142
218
  #{hms_columns}
143
219
  <!-- td>X</td -->
144
220
  </tr>
221
+ </tbody>
145
222
  <% end %>
146
223
  </table>
147
224
 
148
225
  #{"<hr><%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>" unless @_brick_model.is_view?}
149
- "
226
+ #{script}"
150
227
  when 'show'
151
- "<%= @#{@_brick_model.name.underscore}.inspect %>"
228
+ "#{css}
229
+ <p style=\"color: green\"><%= notice %></p>#{"
230
+ <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
231
+ <h1>#{model_name}: <%= (obj = @#{obj_name}.first).brick_descrip %></h1>
232
+ <%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
233
+ <table>
234
+ <% 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(', ')} }
235
+ @#{obj_name}.first.attributes.each do |k, val| %>
236
+ <tr>
237
+ <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
238
+ <th class=\"show-field\">
239
+ <% if (bt = bts[k]) %>
240
+ BT <%= \"#\{bt.first\}-\" unless bt[1].name.underscore == bt.first.to_s %><%= bt[1].name %>
241
+ <% else %>
242
+ <%= k %>
243
+ <% end %>
244
+ </th>
245
+ <td>
246
+ <% if (bt = bts[k]) %>
247
+ <%= bt_obj = bt[1].find_by(bt.last => val); link_to(bt_obj.brick_descrip, bt_obj) if bt_obj %>
248
+ <% else %>
249
+ <%= val %>
250
+ <% end %>
251
+ </td>
252
+ </tr>
253
+ <% end %>
254
+ </table>
255
+
256
+ #{hms_headers.map do |hm|
257
+ next unless (pk = hm.first.klass.primary_key)
258
+ "<table id=\"#{hm_name = hm.first.name.to_s}\">
259
+ <tr><th>#{hm.last}</th></tr>
260
+ <% if (collection = @#{obj_name}.first.#{hm_name}).empty? %>
261
+ <tr><td>(none)</td></tr>
262
+ <% else %>
263
+ <% collection.order(#{pk.inspect}).uniq.each do |#{hm_singular_name = hm_name.singularize}| %>
264
+ <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm_singular_name}_path(#{hm_singular_name}.#{pk})) %></td></tr>
265
+ <% end %>
266
+ <% end %>
267
+ </table>" end.join}
268
+ #{script}"
269
+
152
270
  end
153
271
  # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
154
272
  keys = options.has_key?(:locals) ? options[:locals].keys : []
@@ -194,7 +312,7 @@ module Brick
194
312
  # Find associative tables that can be set up for has_many :through
195
313
  ::Brick.relations.each do |_key, tbl|
196
314
  tbl_cols = tbl[:cols].keys
197
- fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
315
+ fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = [fk.last[:assoc_name], fk.last[:inverse_table]] if fk.last[:is_bt]; s }
198
316
  # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
199
317
  # foreign keys then it can act as an associative table and thus be used with has_many :through.
200
318
  if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - (tbl[:pkey].values.first || [])).length.zero?
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 6
8
+ TINY = 10
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
@@ -86,6 +86,13 @@ module Brick
86
86
  end
87
87
 
88
88
  class << self
89
+ attr_accessor :db_schemas
90
+
91
+ def set_db_schema(params)
92
+ schema = params['_brick_schema'] || 'public'
93
+ ActiveRecord::Base.connection.execute("SET SEARCH_PATH='#{schema}';") if schema && ::Brick.db_schemas&.include?(schema)
94
+ end
95
+
89
96
  # All tables and views (what Postgres calls "relations" including column and foreign key info)
90
97
  def relations
91
98
  connections = Brick.instance_variable_get(:@relations) ||
@@ -98,23 +105,10 @@ module Brick
98
105
  model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
99
106
  case a.macro
100
107
  when :belongs_to
101
- # Build #brick_descrip if needed
102
- if a.klass.instance_methods(false).exclude?(:brick_descrip)
103
- descrip_col = (a.klass.columns.map(&:name) - a.klass._brick_get_fks -
104
- (::Brick.config.metadata_columns || []) -
105
- [a.klass.primary_key]).first&.to_sym
106
- if descrip_col
107
- a.klass.define_method :brick_descrip do
108
- send(descrip_col)
109
- end
110
- end
111
- end
112
-
113
108
  s.first[a.foreign_key] = [a.name, a.klass]
114
109
  when :has_many, :has_one
115
110
  s.last[a.name] = a
116
111
  end
117
- s
118
112
  end
119
113
  end
120
114
 
@@ -180,6 +174,11 @@ module Brick
180
174
  Brick.config.exclude_tables = value
181
175
  end
182
176
 
177
+ # @api public
178
+ def table_name_prefixes=(value)
179
+ Brick.config.table_name_prefixes = value
180
+ end
181
+
183
182
  # @api public
184
183
  def metadata_columns=(value)
185
184
  Brick.config.metadata_columns = value
@@ -196,7 +195,19 @@ module Brick
196
195
  end
197
196
  end
198
197
 
199
- # Associations to treat has a has_one
198
+ # Skip creating a has_many association for these
199
+ # (Uses the same exact three-part format as would define an additional_reference)
200
+ # @api public
201
+ def skip_hms=(skips)
202
+ if skips
203
+ skips = skips.call if skips.is_a?(Proc)
204
+ skips = skips.to_a unless skips.is_a?(Array)
205
+ skips = [skips] unless skips.empty? || skips.first.is_a?(Array)
206
+ Brick.config.skip_hms = skips
207
+ end
208
+ end
209
+
210
+ # Associations to treat as a has_one
200
211
  # @api public
201
212
  def has_ones=(hos)
202
213
  if hos
@@ -211,6 +222,11 @@ module Brick
211
222
  end
212
223
  end
213
224
 
225
+ # DSL templates for individual models to provide prettier descriptions of objects
226
+ # @api public
227
+ def model_descrips=(descrips)
228
+ Brick.config.model_descrips = descrips
229
+ end
214
230
 
215
231
  # Returns Brick's `::Gem::Version`, convenient for comparisons. This is
216
232
  # recommended over `::Brick::VERSION::STRING`.
@@ -19,6 +19,62 @@ module Brick
19
19
 
20
20
  def create_initializer_file
21
21
  unless File.exists?(filename = 'config/initializers/brick.rb')
22
+ # See if we can make suggestions for additional_references
23
+ resembles_fks = []
24
+ possible_additional_references = (relations = ::Brick.relations).each_with_object([]) do |v, s|
25
+ v.last[:cols].each do |col, _type|
26
+ col_down = col.downcase
27
+ is_possible = true
28
+ if col_down.end_with?('_id')
29
+ col_down = col_down[0..-4]
30
+ elsif col_down.end_with?('id')
31
+ col_down = col_down[0..-3]
32
+ is_possible = false if col_down.length < 3 # Was it simply called "id" or something else really short?
33
+ elsif col_down.start_with?('id_')
34
+ col_down = col_down[3..-1]
35
+ elsif col_down.start_with?('id')
36
+ col_down = col_down[2..-1]
37
+ else
38
+ is_possible = false
39
+ end
40
+ if col_down.start_with?('fk_')
41
+ is_possible = true
42
+ col_down = col_down[3..-1]
43
+ end
44
+ # This possible key not really a primary key and not yet used as a foreign key?
45
+ if is_possible && !(relation = relations.fetch(v.first, {}))[:pkey].first&.last&.include?(col) &&
46
+ !relations.fetch(v.first, {})[:fks]&.any? { |_k, v| v[:is_bt] && v[:fk] == col }
47
+ if (relations.fetch(f_table = col_down, nil) ||
48
+ relations.fetch(f_table = ActiveSupport::Inflector.pluralize(col_down), nil)) &&
49
+ # Looks pretty promising ... just make sure a model file isn't present
50
+ !File.exists?("app/models/#{ActiveSupport::Inflector.singularize(v.first)}.rb")
51
+ s << "['#{v.first}', '#{col}', '#{f_table}']"
52
+ else
53
+ resembles_fks << "#{v.first}.#{col}"
54
+ end
55
+ end
56
+ end
57
+ s
58
+ end
59
+
60
+ bar = case possible_additional_references.length
61
+ when 0
62
+ +"# Brick.additional_references = [['orders', 'customer_id', 'customer'],
63
+ # ['customer', 'region_id', 'regions']]"
64
+ when 1
65
+ +"# # Here is a possible additional reference that has been auto-identified for the #{ActiveRecord::Base.connection.current_database} database:
66
+ # Brick.additional_references = [[#{possible_additional_references.first}]"
67
+ else
68
+ +"# # Here are possible additional references that have been auto-identified for the #{ActiveRecord::Base.connection.current_database} database:
69
+ # Brick.additional_references = [
70
+ # #{possible_additional_references.join(",\n# ")}
71
+ # ]"
72
+ end
73
+ if resembles_fks.length > 0
74
+ bar << "\n# # Columns named somewhat like a foreign key which you may want to consider:
75
+ # # #{resembles_fks.join(', ')}"
76
+ end
77
+
22
78
  create_file filename, "# frozen_string_literal: true
23
79
 
24
80
  # # Settings for the Brick gem
@@ -30,12 +86,15 @@ module Brick
30
86
  # Brick.enable_controllers = false
31
87
  # Brick.enable_views = false
32
88
 
33
- # # By default models are auto-created from database views, and set to be read-only. This can be skipped.
89
+ # # By default models are auto-created for database views, and set to be read-only. This can be skipped.
34
90
  # Brick.skip_database_views = true
35
91
 
36
92
  # # Any tables or views you'd like to skip when auto-creating models
37
93
  # Brick.exclude_tables = ['custom_metadata', 'version_info']
38
94
 
95
+ # # When table names have specific prefixes automatically place them in their own module with a table_name_prefix.
96
+ # Brick.table_name_prefixes = { 'nav_' => 'Navigation' }
97
+
39
98
  # # Additional table references which are used to create has_many / belongs_to associations inside auto-created
40
99
  # # models. (You can consider these to be \"virtual foreign keys\" if you wish)... You only have to add these
41
100
  # # in cases where your database for some reason does not have foreign key constraints defined. Sometimes for
@@ -44,9 +103,13 @@ module Brick
44
103
  # # foreign table name / foreign key column / primary table name.
45
104
  # # (We boldly expect that the primary key identified by ActiveRecord on the primary table will be accurate,
46
105
  # # usually this is \"id\" but there are some good smarts that are used in case some other column has been set
47
- # # to be the primary key.
48
- # Brick.additional_references = [['orders', 'customer_id', 'customer'],
49
- # ['customer', 'region_id', 'regions']]
106
+ # # to be the primary key.)
107
+ #{bar}
108
+
109
+ # # Skip creating a has_many association for these
110
+ # # (Uses the same exact three-part format as would define an additional_reference)
111
+ # # Say for instance that we didn't care to display the favourite colours that users have:
112
+ # Brick.skip_hms = [['users', 'favourite_colour_id', 'colours']]
50
113
 
51
114
  # # By default primary tables involved in a foreign key relationship will indicate a \"has_many\" relationship pointing
52
115
  # # back to the foreign table. In order to represent a \"has_one\" association instead, an override can be provided
@@ -56,12 +119,20 @@ module Brick
56
119
  # # instead of \"user_profile\", then apply that as a third parameter like this:
57
120
  # Brick.has_ones = [['User', 'user_profile', 'profile']]
58
121
 
59
- # # We normally don't consider the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\" to count when
60
- # # finding tables which can serve as associative tables in an N:M association. That is, ones that can be a
61
- # # part of a has_many :through association. If you want to use different exclusion columns than our defaults
62
- # # then this setting resets that list. For instance, here is the override for the Sakila sample database:
122
+ # # We normally don't show the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\", and also do
123
+ # # not consider them when finding associative tables to support an N:M association. (That is, ones that can be a
124
+ # # part of a has_many :through association.) If you want to use different exclusion columns than our defaults
125
+ # # then this setting resets that list. For instance, here is an override that is useful in the Sakila sample
126
+ # # database:
63
127
  # Brick.metadata_columns = ['last_updated']
64
128
 
129
+ # # A simple DSL is available to allow more user-friendly display of objects. Normally a user object might be shown
130
+ # # as its first non-metadata column, or if that is not available then something like \"User #45\" where 45 is that
131
+ # # object's ID. If there is no primary key then even that is not possible, so the object's .to_s method is called.
132
+ # # To override these defaults and specify exactly what you want shown, such as first names and last names for a
133
+ # # user, then you can use model_descrips like this, putting expressions with property references in square brackets:
134
+ # Brick.model_descrips = { 'User' => '[profile.firstname] [profile.lastname]' }
135
+
65
136
  # # If a default route is not supplied, Brick attempts to find the most \"central\" table and wires up the default
66
137
  # # route to go to the :index action for what would be a controller for that table. You can specify any controller
67
138
  # # 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.6
4
+ version: 1.0.10
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-19 00:00:00.000000000 Z
11
+ date: 2022-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord