brick 1.0.8 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
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