brick 1.0.8 → 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: 7e06e6aaa9b7fef7730d56404912ab186a29f6e698f6dbb0d9c551cf2008c0b1
4
- data.tar.gz: 7e546071063729d5b85b2f146bb401c7cbfc462f6dea0a701ed8c839cb883de5
3
+ metadata.gz: cfc0c2e81700a407de1fe154d337dd83d756a29fba0914db5eeb1974198720d2
4
+ data.tar.gz: 0051e6b4fe55d1e9f4cc4614aabd941a6947f66af5ac41f35725df594217fd2d
5
5
  SHA512:
6
- metadata.gz: 83499e7959f98bb323f44593f6472ba1996815ed5f286a119f5adfbd2b690be3f9cd3556166cff3efda250fd609b6d06f4054aea599b0e9587191b4cb1d7a3cb
7
- data.tar.gz: 6ded71697d4cce4d931afb54869639d6c2d0b32462806c0c0c7826ceb46fd5f7d7c2476f29e5c9302c83482e0293916dd82e553fa847482de52c39e06dc2d210
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
+ # 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
+
68
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,93 +247,96 @@ 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
- # Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
198
- hmts = fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts|
199
- # The key in each hash entry (fk.first) is the constraint name
200
- assoc_name = (assoc = fk.last)[:assoc_name]
201
- inverse_assoc_name = assoc[:inverse][:assoc_name]
202
- options = {}
203
- singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
204
- macro = if assoc[:is_bt]
205
- need_class_name = singular_table_name.underscore != assoc_name
206
- need_fk = "#{assoc_name}_id" != assoc[:fk]
207
- inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], assoc[:inverse])
208
- 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))
209
- inverse_assoc_name = if has_ones[singular_inv_assoc_name]
210
- need_inverse_of = true
211
- has_ones[singular_inv_assoc_name]
212
- else
213
- singular_inv_assoc_name
214
- end
215
- end
216
- :belongs_to
217
- else
218
- # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
219
- # Are there multiple foreign keys out to the same table?
220
- assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
221
- need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
222
- # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
223
- if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
224
- assoc_name = if has_ones[singular_assoc_name]
225
- need_class_name = true
226
- has_ones[singular_assoc_name]
227
- else
228
- singular_assoc_name
229
- end
230
- :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
231
274
  else
232
- :has_many
233
- end
234
- end
235
- # Figure out if we need to specially call out the class_name and/or foreign key
236
- # (and if either of those then definitely also a specific inverse_of)
237
- options[:class_name] = singular_table_name.camelize if need_class_name
238
- # Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
239
- if need_fk # Funky foreign key?
240
- options[:foreign_key] = if assoc[:fk].is_a?(Array)
241
- assoc_fk = assoc[:fk].uniq
242
- assoc_fk.length < 2 ? assoc_fk.first : assoc_fk
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]
243
284
  else
244
- assoc[:fk].to_sym
285
+ singular_assoc_name
245
286
  end
246
- end
247
- options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk || need_inverse_of
287
+ :has_one
288
+ else
289
+ :has_many
290
+ end
291
+ end
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)
248
305
 
249
- # Prepare a list of entries for "has_many :through"
250
- if macro == :has_many
251
- relations[assoc[:inverse_table]][:hmt_fks].each do |k, hmt_fk|
252
- next if k == assoc[:fk]
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]
253
310
 
254
- hmts[ActiveSupport::Inflector.pluralize(hmt_fk.last)] << [assoc, hmt_fk.first]
311
+ hmts[ActiveSupport::Inflector.pluralize(hmt_fk.last)] << [assoc, hmt_fk.first]
312
+ end
255
313
  end
256
- end
257
314
 
258
- # And finally create a has_one, has_many, or belongs_to for this association
259
- assoc_name = assoc_name.to_sym
260
- code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
261
- self.send(macro, assoc_name, **options)
262
- hmts
263
- end
264
- hmts.each do |hmt_fk, fks|
265
- fks.each do |fk|
266
- source = nil
267
- this_hmt_fk = if fks.length > 1
268
- singular_assoc_name = ActiveSupport::Inflector.singularize(fk.first[:inverse][:assoc_name])
269
- source = fk.last
270
- through = ActiveSupport::Inflector.pluralize(fk.first[:alternate_name])
271
- "#{singular_assoc_name}_#{hmt_fk}"
272
- else
273
- through = fk.first[:assoc_name]
274
- hmt_fk
275
- end
276
- code << " has_many :#{this_hmt_fk}, through: #{(assoc_name = through.to_sym).to_sym.inspect}#{", source: :#{source}" if source}\n"
277
- options = { through: assoc_name }
278
- options[:source] = source.to_sym if source
279
- self.send(:has_many, this_hmt_fk.to_sym, **options)
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)
337
+ end
280
338
  end
281
339
  end
282
-
283
340
  code << "end # model #{model_name}\n\n"
284
341
  end # class definition
285
342
  [built_model, code]
@@ -298,6 +355,7 @@ class Object
298
355
  code << " @#{table_name}.brick_where(params)\n"
299
356
  code << " end\n"
300
357
  self.define_method :index do
358
+ ::Brick.set_db_schema(params)
301
359
  ar_relation = model.primary_key ? model.order(model.primary_key) : model.all
302
360
  instance_variable_set(:@_brick_params, ar_relation.brick_where(params))
303
361
  instance_variable_set("@#{table_name}".to_sym, ar_relation)
@@ -308,6 +366,7 @@ class Object
308
366
  code << " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n"
309
367
  code << " end\n"
310
368
  self.define_method :show do
369
+ ::Brick.set_db_schema(params)
311
370
  instance_variable_set("@#{singular_table_name}".to_sym, model.find(params[:id].split(',')))
312
371
  end
313
372
  end
@@ -323,6 +382,7 @@ class Object
323
382
  end
324
383
 
325
384
  def _brick_get_hm_assoc_name(relation, hm_assoc)
385
+ binding.pry if hm_assoc.nil?
326
386
  if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
327
387
  [ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name]), true]
328
388
  else
@@ -349,9 +409,11 @@ module ActiveRecord::ConnectionHandling
349
409
  # Only for Postgres? (Doesn't work in sqlite3)
350
410
  # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
351
411
 
352
- case ActiveRecord::Base.connection.adapter_name
412
+ schema_sql = 'SELECT NULL AS table_schema;'
413
+ case ActiveRecord::Base.connection.adapter_name
353
414
  when 'PostgreSQL'
354
415
  schema = 'public'
416
+ schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
355
417
  when 'Mysql2'
356
418
  schema = ActiveRecord::Base.connection.current_database
357
419
  when 'SQLite'
@@ -459,6 +521,10 @@ module ActiveRecord::ConnectionHandling
459
521
  else
460
522
  end
461
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']
462
528
  ActiveRecord::Base.connection.execute(sql).each do |fk|
463
529
  fk = fk.values unless fk.is_a?(Array)
464
530
  ::Brick._add_bt_and_hm(fk, relations)
@@ -499,65 +565,69 @@ module Brick
499
565
  end # module Extensions
500
566
  # rubocop:enable Style/CommentedKeyword
501
567
 
502
- def self._add_bt_and_hm(fk, relations = nil)
503
- relations ||= ::Brick.relations
504
- bt_assoc_name = fk[1].underscore
505
- bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
506
-
507
- bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
508
- hms = (relation = relations.fetch(fk[2], nil))&.fetch(:fks) { relation[:fks] = {} }
509
-
510
- unless (cnstr_name = fk[3])
511
- # For any appended references (those that come from config), arrive upon a definitely unique constraint name
512
- cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{fk[2]}"
513
- cnstr_added_num = 1
514
- cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
515
- missing = []
516
- missing << fk[0] unless relations.key?(fk[0])
517
- missing << fk[2] unless relations.key?(fk[2])
518
- unless missing.empty?
519
- tables = relations.reject { |k, v| v.fetch(:isView, nil) }.keys.sort
520
- puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
521
- 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
522
599
  end
523
- unless (cols = relations[fk[0]][:cols]).key?(fk[1])
524
- columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
525
- puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
526
- 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] }
527
605
  end
528
- if (redundant = bts.find{|k, v| v[:inverse][:inverse_table] == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == fk[2] })
529
- puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed. (Already established by #{redundant.first}.)"
530
- 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
531
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] }
532
621
  end
533
622
 
534
- if (assoc_bt = bts[cnstr_name])
535
- assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[1]] : assoc_bt[:fk].concat(fk[1])
536
- assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
537
- else
538
- 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
539
626
  end
540
627
 
541
- if (assoc_hm = hms[cnstr_name])
542
- assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
543
- assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
544
- assoc_hm[:inverse] = assoc_bt
545
- else
546
- 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 }
547
- hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
548
- 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
549
631
  end
550
- assoc_bt[:inverse] = assoc_hm
551
- # hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
552
- end
553
-
554
- # Rails < 4.0 doesn't have ActiveRecord::RecordNotUnique, so use the more generic ActiveRecord::ActiveRecordError instead
555
- ar_not_unique_error = ActiveRecord.const_defined?('RecordNotUnique') ? ActiveRecord::RecordNotUnique : ActiveRecord::ActiveRecordError
556
- class NoUniqueColumnError < ar_not_unique_error
557
- end
558
-
559
- # Rails < 4.2 doesn't have ActiveRecord::RecordInvalid, so use the more generic ActiveRecord::ActiveRecordError instead
560
- ar_invalid_error = ActiveRecord.const_defined?('RecordInvalid') ? ActiveRecord::RecordInvalid : ActiveRecord::ActiveRecordError
561
- class LessThanHalfAreMatchingColumnsError < ar_invalid_error
562
632
  end
563
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
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
82
+ end
68
83
  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
76
- end
77
- s << if hm.last.macro == :has_many
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 : []
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 8
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,6 +195,18 @@ module Brick
196
195
  end
197
196
  end
198
197
 
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
+
199
210
  # Associations to treat as a has_one
200
211
  # @api public
201
212
  def has_ones=(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.8
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-20 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