brick 1.0.8 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +27 -2
- data/lib/brick/extensions.rb +233 -142
- data/lib/brick/frameworks/rails/engine.rb +173 -51
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +29 -13
- data/lib/generators/brick/install_generator.rb +86 -13
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7c3e4032e5e9f111839b7ff6e6378e4937edeabeb0edcf480cf6c50e79e599a
|
4
|
+
data.tar.gz: 1a1e5b096a3f6fff0f2c49c6be6abcfbe012f3e24fccf0ea6b307b58d9cbc076
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dbd885a2c4dc9666378a69423179ca2085c89b6b6120d21ef8d3274f229a88306f54868b8a18aad8e55a37a71bc9b78dff073263bb86fd89d5b982acf8d5210
|
7
|
+
data.tar.gz: 952f94f89445170f26fad10357ede0df7c9053d034ddb552b1e9cba884cbdf0325325cb29a2248fac4e9de3bd8c41619fc9612d44435e6ccb808a16fd018d8dc
|
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=(
|
74
|
-
@mutex.synchronize { @has_ones =
|
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
|
data/lib/brick/extensions.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -88,7 +137,26 @@ module ActiveRecord
|
|
88
137
|
alias _brick_find_sti_class find_sti_class
|
89
138
|
def find_sti_class(type_name)
|
90
139
|
::Brick.sti_models[type_name] = { base: self } unless type_name.blank?
|
91
|
-
|
140
|
+
module_prefixes = type_name.split('::')
|
141
|
+
module_prefixes.unshift('') unless module_prefixes.first.blank?
|
142
|
+
candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
|
143
|
+
if File.exists?(candidate_file)
|
144
|
+
# Find this STI class normally
|
145
|
+
_brick_find_sti_class(type_name)
|
146
|
+
else
|
147
|
+
# Build missing prefix modules if they don't yet exist
|
148
|
+
this_module = Object
|
149
|
+
module_prefixes[1..-2].each do |module_name|
|
150
|
+
mod = if this_module.const_defined?(module_name)
|
151
|
+
this_module.const_get(module_name)
|
152
|
+
else
|
153
|
+
this_module.const_set(module_name.to_sym, Module.new)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
# Build missing prefix modules if they don't yet exist
|
157
|
+
this_module.const_set(module_prefixes.last.to_sym, klass = Class.new(self))
|
158
|
+
klass
|
159
|
+
end
|
92
160
|
end
|
93
161
|
end
|
94
162
|
end
|
@@ -112,7 +180,8 @@ class Object
|
|
112
180
|
return Object._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(class_name.underscore)
|
113
181
|
|
114
182
|
relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
|
115
|
-
|
183
|
+
is_controllers_enabled = Rails.development? || ::Brick.enable_controllers?
|
184
|
+
result = if is_controllers_enabled && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
116
185
|
# Otherwise now it's up to us to fill in the gaps
|
117
186
|
if (model = ActiveSupport::Inflector.singularize(plural_class_name).constantize)
|
118
187
|
# if it's a controller and no match or a model doesn't really use the same table name, eager load all models and try to find a model class of the right name.
|
@@ -123,12 +192,13 @@ class Object
|
|
123
192
|
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
124
193
|
plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
125
194
|
singular_table_name = ActiveSupport::Inflector.underscore(model_name)
|
126
|
-
table_name = ActiveSupport::Inflector.pluralize(singular_table_name)
|
127
195
|
|
128
196
|
# 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
|
-
|
131
|
-
|
197
|
+
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
|
198
|
+
base_model.table_name
|
199
|
+
else
|
200
|
+
ActiveSupport::Inflector.pluralize(singular_table_name)
|
201
|
+
end
|
132
202
|
|
133
203
|
# Maybe, just maybe there's a database table that will satisfy this need
|
134
204
|
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
|
@@ -155,7 +225,11 @@ class Object
|
|
155
225
|
if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
|
156
226
|
raise NameError.new("Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\".")
|
157
227
|
end
|
158
|
-
base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil)
|
228
|
+
if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
|
229
|
+
is_sti = true
|
230
|
+
else
|
231
|
+
base_model = ActiveRecord::Base
|
232
|
+
end
|
159
233
|
code = +"class #{model_name} < #{base_model.name}\n"
|
160
234
|
built_model = Class.new(base_model) do |new_model_class|
|
161
235
|
Object.const_set(model_name.to_sym, new_model_class)
|
@@ -193,93 +267,96 @@ class Object
|
|
193
267
|
code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
|
194
268
|
end
|
195
269
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
270
|
+
unless is_sti
|
271
|
+
fks = relation[:fks] || {}
|
272
|
+
# Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
|
273
|
+
hmts = fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts|
|
274
|
+
# The key in each hash entry (fk.first) is the constraint name
|
275
|
+
assoc_name = (assoc = fk.last)[:assoc_name]
|
276
|
+
inverse_assoc_name = assoc[:inverse]&.fetch(:assoc_name, nil)
|
277
|
+
options = {}
|
278
|
+
singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
|
279
|
+
macro = if assoc[:is_bt]
|
280
|
+
need_class_name = singular_table_name.underscore != assoc_name
|
281
|
+
need_fk = "#{assoc_name}_id" != assoc[:fk]
|
282
|
+
if (inverse = assoc[:inverse])
|
283
|
+
inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], inverse)
|
284
|
+
if (has_ones = ::Brick.config.has_ones&.fetch(inverse[:alternate_name].camelize, nil))&.key?(singular_inv_assoc_name = ActiveSupport::Inflector.singularize(inverse_assoc_name))
|
285
|
+
inverse_assoc_name = if has_ones[singular_inv_assoc_name]
|
286
|
+
need_inverse_of = true
|
287
|
+
has_ones[singular_inv_assoc_name]
|
288
|
+
else
|
289
|
+
singular_inv_assoc_name
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
:belongs_to
|
231
294
|
else
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
assoc_fk = assoc[:fk].uniq
|
242
|
-
assoc_fk.length < 2 ? assoc_fk.first : assoc_fk
|
295
|
+
# need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
|
296
|
+
# Are there multiple foreign keys out to the same table?
|
297
|
+
assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
|
298
|
+
need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
|
299
|
+
# fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
|
300
|
+
if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
|
301
|
+
assoc_name = if has_ones[singular_assoc_name]
|
302
|
+
need_class_name = true
|
303
|
+
has_ones[singular_assoc_name]
|
243
304
|
else
|
244
|
-
|
305
|
+
singular_assoc_name
|
245
306
|
end
|
246
|
-
|
247
|
-
|
307
|
+
:has_one
|
308
|
+
else
|
309
|
+
:has_many
|
310
|
+
end
|
311
|
+
end
|
312
|
+
# Figure out if we need to specially call out the class_name and/or foreign key
|
313
|
+
# (and if either of those then definitely also a specific inverse_of)
|
314
|
+
options[:class_name] = singular_table_name.camelize if need_class_name
|
315
|
+
# Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
|
316
|
+
if need_fk # Funky foreign key?
|
317
|
+
options[:foreign_key] = if assoc[:fk].is_a?(Array)
|
318
|
+
assoc_fk = assoc[:fk].uniq
|
319
|
+
assoc_fk.length < 2 ? assoc_fk.first : assoc_fk
|
320
|
+
else
|
321
|
+
assoc[:fk].to_sym
|
322
|
+
end
|
323
|
+
end
|
324
|
+
options[:inverse_of] = inverse_assoc_name.to_sym if inverse_assoc_name && (need_class_name || need_fk || need_inverse_of)
|
248
325
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
326
|
+
# Prepare a list of entries for "has_many :through"
|
327
|
+
if macro == :has_many
|
328
|
+
relations[assoc[:inverse_table]][:hmt_fks].each do |k, hmt_fk|
|
329
|
+
next if k == assoc[:fk]
|
253
330
|
|
254
|
-
|
331
|
+
hmts[ActiveSupport::Inflector.pluralize(hmt_fk.last)] << [assoc, hmt_fk.first]
|
332
|
+
end
|
255
333
|
end
|
256
|
-
end
|
257
334
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
335
|
+
# And finally create a has_one, has_many, or belongs_to for this association
|
336
|
+
assoc_name = assoc_name.to_sym
|
337
|
+
code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
|
338
|
+
self.send(macro, assoc_name, **options)
|
339
|
+
hmts
|
340
|
+
end
|
341
|
+
hmts.each do |hmt_fk, fks|
|
342
|
+
fks.each do |fk|
|
343
|
+
source = nil
|
344
|
+
this_hmt_fk = if fks.length > 1
|
345
|
+
singular_assoc_name = ActiveSupport::Inflector.singularize(fk.first[:inverse][:assoc_name])
|
346
|
+
source = fk.last
|
347
|
+
through = ActiveSupport::Inflector.pluralize(fk.first[:alternate_name])
|
348
|
+
"#{singular_assoc_name}_#{hmt_fk}"
|
349
|
+
else
|
350
|
+
through = fk.first[:assoc_name]
|
351
|
+
hmt_fk
|
352
|
+
end
|
353
|
+
code << " has_many :#{this_hmt_fk}, through: #{(assoc_name = through.to_sym).to_sym.inspect}#{", source: :#{source}" if source}\n"
|
354
|
+
options = { through: assoc_name }
|
355
|
+
options[:source] = source.to_sym if source
|
356
|
+
self.send(:has_many, this_hmt_fk.to_sym, **options)
|
357
|
+
end
|
280
358
|
end
|
281
359
|
end
|
282
|
-
|
283
360
|
code << "end # model #{model_name}\n\n"
|
284
361
|
end # class definition
|
285
362
|
[built_model, code]
|
@@ -298,6 +375,7 @@ class Object
|
|
298
375
|
code << " @#{table_name}.brick_where(params)\n"
|
299
376
|
code << " end\n"
|
300
377
|
self.define_method :index do
|
378
|
+
::Brick.set_db_schema(params)
|
301
379
|
ar_relation = model.primary_key ? model.order(model.primary_key) : model.all
|
302
380
|
instance_variable_set(:@_brick_params, ar_relation.brick_where(params))
|
303
381
|
instance_variable_set("@#{table_name}".to_sym, ar_relation)
|
@@ -308,6 +386,7 @@ class Object
|
|
308
386
|
code << " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n"
|
309
387
|
code << " end\n"
|
310
388
|
self.define_method :show do
|
389
|
+
::Brick.set_db_schema(params)
|
311
390
|
instance_variable_set("@#{singular_table_name}".to_sym, model.find(params[:id].split(',')))
|
312
391
|
end
|
313
392
|
end
|
@@ -349,9 +428,11 @@ module ActiveRecord::ConnectionHandling
|
|
349
428
|
# Only for Postgres? (Doesn't work in sqlite3)
|
350
429
|
# puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
351
430
|
|
352
|
-
|
431
|
+
schema_sql = 'SELECT NULL AS table_schema;'
|
432
|
+
case ActiveRecord::Base.connection.adapter_name
|
353
433
|
when 'PostgreSQL'
|
354
434
|
schema = 'public'
|
435
|
+
schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
|
355
436
|
when 'Mysql2'
|
356
437
|
schema = ActiveRecord::Base.connection.current_database
|
357
438
|
when 'SQLite'
|
@@ -459,6 +540,10 @@ module ActiveRecord::ConnectionHandling
|
|
459
540
|
else
|
460
541
|
end
|
461
542
|
if sql
|
543
|
+
::Brick.db_schemas = ActiveRecord::Base.connection.execute(schema_sql)
|
544
|
+
::Brick.db_schemas = ::Brick.db_schemas.to_a unless ::Brick.db_schemas.is_a?(Array)
|
545
|
+
::Brick.db_schemas.map! { |row| row['table_schema'] } unless ::Brick.db_schemas.empty? || ::Brick.db_schemas.first.is_a?(String)
|
546
|
+
::Brick.db_schemas -= ['information_schema', 'pg_catalog']
|
462
547
|
ActiveRecord::Base.connection.execute(sql).each do |fk|
|
463
548
|
fk = fk.values unless fk.is_a?(Array)
|
464
549
|
::Brick._add_bt_and_hm(fk, relations)
|
@@ -466,10 +551,12 @@ module ActiveRecord::ConnectionHandling
|
|
466
551
|
end
|
467
552
|
end
|
468
553
|
|
469
|
-
puts "
|
554
|
+
puts "\nClasses that can be built from tables:"
|
470
555
|
relations.select { |_k, v| !v.key?(:isView) }.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
|
471
|
-
|
472
|
-
|
556
|
+
unless (views = relations.select { |_k, v| v.key?(:isView) }).empty?
|
557
|
+
puts "\nClasses that can be built from views:"
|
558
|
+
views.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
|
559
|
+
end
|
473
560
|
# pp relations; nil
|
474
561
|
|
475
562
|
# relations.keys.each { |k| ActiveSupport::Inflector.singularize(k).camelize.constantize }
|
@@ -499,65 +586,69 @@ module Brick
|
|
499
586
|
end # module Extensions
|
500
587
|
# rubocop:enable Style/CommentedKeyword
|
501
588
|
|
502
|
-
|
503
|
-
relations
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
589
|
+
class << self
|
590
|
+
def _add_bt_and_hm(fk, relations = nil)
|
591
|
+
relations ||= ::Brick.relations
|
592
|
+
bt_assoc_name = fk[1].underscore
|
593
|
+
bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
|
594
|
+
|
595
|
+
bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
|
596
|
+
hms = (relation = relations.fetch(fk[2], nil))&.fetch(:fks) { relation[:fks] = {} }
|
597
|
+
|
598
|
+
unless (cnstr_name = fk[3])
|
599
|
+
# For any appended references (those that come from config), arrive upon a definitely unique constraint name
|
600
|
+
cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{fk[2]}"
|
601
|
+
cnstr_added_num = 1
|
602
|
+
cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
|
603
|
+
missing = []
|
604
|
+
missing << fk[0] unless relations.key?(fk[0])
|
605
|
+
missing << fk[2] unless relations.key?(fk[2])
|
606
|
+
unless missing.empty?
|
607
|
+
tables = relations.reject { |k, v| v.fetch(:isView, nil) }.keys.sort
|
608
|
+
puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
|
609
|
+
return
|
610
|
+
end
|
611
|
+
unless (cols = relations[fk[0]][:cols]).key?(fk[1])
|
612
|
+
columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
|
613
|
+
puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
|
614
|
+
return
|
615
|
+
end
|
616
|
+
if (redundant = bts.find { |k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == fk[2] })
|
617
|
+
puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed. (Already established by #{redundant.first}.)"
|
618
|
+
return
|
619
|
+
end
|
522
620
|
end
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
621
|
+
if (assoc_bt = bts[cnstr_name])
|
622
|
+
assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[1]] : assoc_bt[:fk].concat(fk[1])
|
623
|
+
assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
|
624
|
+
else
|
625
|
+
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: fk[2] }
|
527
626
|
end
|
528
|
-
|
529
|
-
|
530
|
-
|
627
|
+
|
628
|
+
unless ::Brick.config.skip_hms&.any? { |skip| fk[0] == skip[0] && fk[1] == skip[1] && fk[2] == skip[2] }
|
629
|
+
cnstr_name = "hm_#{cnstr_name}"
|
630
|
+
if (assoc_hm = hms.fetch(cnstr_name, nil))
|
631
|
+
assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
|
632
|
+
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
633
|
+
assoc_hm[:inverse] = assoc_bt
|
634
|
+
else
|
635
|
+
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 }
|
636
|
+
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
637
|
+
hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
|
638
|
+
end
|
639
|
+
assoc_bt[:inverse] = assoc_hm
|
531
640
|
end
|
641
|
+
# hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
|
532
642
|
end
|
533
643
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
else
|
538
|
-
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: fk[2] }
|
644
|
+
# Rails < 4.0 doesn't have ActiveRecord::RecordNotUnique, so use the more generic ActiveRecord::ActiveRecordError instead
|
645
|
+
ar_not_unique_error = ActiveRecord.const_defined?('RecordNotUnique') ? ActiveRecord::RecordNotUnique : ActiveRecord::ActiveRecordError
|
646
|
+
class NoUniqueColumnError < ar_not_unique_error
|
539
647
|
end
|
540
648
|
|
541
|
-
|
542
|
-
|
543
|
-
|
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
|
649
|
+
# Rails < 4.2 doesn't have ActiveRecord::RecordInvalid, so use the more generic ActiveRecord::ActiveRecordError instead
|
650
|
+
ar_invalid_error = ActiveRecord.const_defined?('RecordInvalid') ? ActiveRecord::RecordInvalid : ActiveRecord::ActiveRecordError
|
651
|
+
class LessThanHalfAreMatchingColumnsError < ar_invalid_error
|
549
652
|
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
653
|
end
|
563
654
|
end
|
@@ -8,20 +8,26 @@ module Brick
|
|
8
8
|
config.brick = ActiveSupport::OrderedOptions.new
|
9
9
|
ActiveSupport.on_load(:before_initialize) do |app|
|
10
10
|
::Brick.enable_models = app.config.brick.fetch(:enable_models, true)
|
11
|
-
::Brick.enable_controllers = app.config.brick.fetch(:enable_controllers,
|
12
|
-
::Brick.enable_views = app.config.brick.fetch(:enable_views,
|
13
|
-
::Brick.enable_routes = app.config.brick.fetch(:enable_routes,
|
11
|
+
::Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, false)
|
12
|
+
::Brick.enable_views = app.config.brick.fetch(:enable_views, false)
|
13
|
+
::Brick.enable_routes = app.config.brick.fetch(:enable_routes, false)
|
14
14
|
::Brick.skip_database_views = app.config.brick.fetch(:skip_database_views, false)
|
15
15
|
|
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
|
@@ -31,7 +37,7 @@ module Brick
|
|
31
37
|
# ====================================
|
32
38
|
# Dynamically create generic templates
|
33
39
|
# ====================================
|
34
|
-
if ::Brick.enable_views?
|
40
|
+
if Rails.development? || ::Brick.enable_views?
|
35
41
|
ActionView::LookupContext.class_exec do
|
36
42
|
alias :_brick_template_exists? :template_exists?
|
37
43
|
def template_exists?(*args, **options)
|
@@ -62,78 +68,152 @@ 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
|
-
#
|
66
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
<%
|
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
|
-
|
116
|
-
|
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
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
212
|
+
<%# Instead of just 'bt_obj we have to put in all of this junk:
|
213
|
+
# send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))
|
214
|
+
# Otherwise we get stuff like:
|
215
|
+
# ActionView::Template::Error (undefined method `vehicle_path' for #<ActionView::Base:0x0000000033a888>) %>
|
216
|
+
<%= bt_obj = bt[1].find_by(bt.last => val); link_to(bt_obj.brick_descrip, send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))) if bt_obj %>
|
137
217
|
<% else %>
|
138
218
|
<%= val %>
|
139
219
|
<% end %>
|
@@ -142,13 +222,55 @@ module Brick
|
|
142
222
|
#{hms_columns}
|
143
223
|
<!-- td>X</td -->
|
144
224
|
</tr>
|
225
|
+
</tbody>
|
145
226
|
<% end %>
|
146
227
|
</table>
|
147
228
|
|
148
229
|
#{"<hr><%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>" unless @_brick_model.is_view?}
|
149
|
-
"
|
230
|
+
#{script}"
|
150
231
|
when 'show'
|
151
|
-
|
232
|
+
"#{css}
|
233
|
+
<p style=\"color: green\"><%= notice %></p>#{"
|
234
|
+
<select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
|
235
|
+
<h1>#{model_name}: <%= (obj = @#{obj_name}.first).brick_descrip %></h1>
|
236
|
+
<%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
|
237
|
+
<table>
|
238
|
+
<% bts = { #{bts.each_with_object([]) { |v, s| s << "#{v.first.inspect} => [#{v.last.first.inspect}, #{v.last[1].name}, #{v.last[1].primary_key.inspect}]"}.join(', ')} }
|
239
|
+
@#{obj_name}.first.attributes.each do |k, val| %>
|
240
|
+
<tr>
|
241
|
+
<% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
|
242
|
+
<th class=\"show-field\">
|
243
|
+
<% if (bt = bts[k]) %>
|
244
|
+
BT <%= \"#\{bt.first\}-\" unless bt[1].name.underscore == bt.first.to_s %><%= bt[1].name %>
|
245
|
+
<% else %>
|
246
|
+
<%= k %>
|
247
|
+
<% end %>
|
248
|
+
</th>
|
249
|
+
<td>
|
250
|
+
<% if (bt = bts[k]) %>
|
251
|
+
<%= bt_obj = bt[1].find_by(bt.last => val); link_to(bt_obj.brick_descrip, send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))) if bt_obj %>
|
252
|
+
<% else %>
|
253
|
+
<%= val %>
|
254
|
+
<% end %>
|
255
|
+
</td>
|
256
|
+
</tr>
|
257
|
+
<% end %>
|
258
|
+
</table>
|
259
|
+
|
260
|
+
#{hms_headers.map do |hm|
|
261
|
+
next unless (pk = hm.first.klass.primary_key)
|
262
|
+
"<table id=\"#{hm_name = hm.first.name.to_s}\">
|
263
|
+
<tr><th>#{hm.last}</th></tr>
|
264
|
+
<% if (collection = @#{obj_name}.first.#{hm_name}).empty? %>
|
265
|
+
<tr><td>(none)</td></tr>
|
266
|
+
<% else %>
|
267
|
+
<% collection.order(#{pk.inspect}).uniq.each do |#{hm_singular_name = hm_name.singularize}| %>
|
268
|
+
<tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm_singular_name}_path(#{hm_singular_name}.#{pk})) %></td></tr>
|
269
|
+
<% end %>
|
270
|
+
<% end %>
|
271
|
+
</table>" end.join}
|
272
|
+
#{script}"
|
273
|
+
|
152
274
|
end
|
153
275
|
# As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
|
154
276
|
keys = options.has_key?(:locals) ? options[:locals].keys : []
|
@@ -161,7 +283,7 @@ module Brick
|
|
161
283
|
end
|
162
284
|
end
|
163
285
|
|
164
|
-
if ::Brick.enable_routes?
|
286
|
+
if Rails.development? || ::Brick.enable_routes?
|
165
287
|
ActionDispatch::Routing::RouteSet.class_exec do
|
166
288
|
alias _brick_finalize_routeset! finalize!
|
167
289
|
def finalize!(*args, **options)
|
data/lib/brick/version_number.rb
CHANGED
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,23 +19,84 @@ 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
|
25
81
|
# # (By default this auto-creates models, controllers, views, and routes on-the-fly.)
|
26
82
|
|
27
|
-
# # Normally
|
28
|
-
#
|
83
|
+
# # Normally all are enabled in development mode, and for security reasons only models are enabled in production
|
84
|
+
# # and test. This allows you to either (a) turn off models entirely, or (b) enable controllers, views, and routes
|
85
|
+
# # in production.
|
86
|
+
# Brick.enable_routes = true # Setting this to \"false\" will disable routes in development
|
29
87
|
# Brick.enable_models = false
|
30
|
-
# Brick.enable_controllers = false
|
31
|
-
# Brick.enable_views = false
|
88
|
+
# Brick.enable_controllers = true # Setting this to \"false\" will disable controllers in development
|
89
|
+
# Brick.enable_views = true # Setting this to \"false\" will disable views in development
|
32
90
|
|
33
|
-
# # By default models are auto-created
|
91
|
+
# # By default models are auto-created for database views, and set to be read-only. This can be skipped.
|
34
92
|
# Brick.skip_database_views = true
|
35
93
|
|
36
94
|
# # Any tables or views you'd like to skip when auto-creating models
|
37
95
|
# Brick.exclude_tables = ['custom_metadata', 'version_info']
|
38
96
|
|
97
|
+
# # When table names have specific prefixes automatically place them in their own module with a table_name_prefix.
|
98
|
+
# Brick.table_name_prefixes = { 'nav_' => 'Navigation' }
|
99
|
+
|
39
100
|
# # Additional table references which are used to create has_many / belongs_to associations inside auto-created
|
40
101
|
# # models. (You can consider these to be \"virtual foreign keys\" if you wish)... You only have to add these
|
41
102
|
# # in cases where your database for some reason does not have foreign key constraints defined. Sometimes for
|
@@ -44,9 +105,13 @@ module Brick
|
|
44
105
|
# # foreign table name / foreign key column / primary table name.
|
45
106
|
# # (We boldly expect that the primary key identified by ActiveRecord on the primary table will be accurate,
|
46
107
|
# # 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
|
-
#
|
49
|
-
|
108
|
+
# # to be the primary key.)
|
109
|
+
#{bar}
|
110
|
+
|
111
|
+
# # Skip creating a has_many association for these
|
112
|
+
# # (Uses the same exact three-part format as would define an additional_reference)
|
113
|
+
# # Say for instance that we didn't care to display the favourite colours that users have:
|
114
|
+
# Brick.skip_hms = [['users', 'favourite_colour_id', 'colours']]
|
50
115
|
|
51
116
|
# # By default primary tables involved in a foreign key relationship will indicate a \"has_many\" relationship pointing
|
52
117
|
# # back to the foreign table. In order to represent a \"has_one\" association instead, an override can be provided
|
@@ -56,11 +121,19 @@ module Brick
|
|
56
121
|
# # instead of \"user_profile\", then apply that as a third parameter like this:
|
57
122
|
# Brick.has_ones = [['User', 'user_profile', 'profile']]
|
58
123
|
|
59
|
-
# # We normally don't
|
60
|
-
# #
|
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
|
63
|
-
#
|
124
|
+
# # We normally don't show the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\", and also do
|
125
|
+
# # not consider them when finding associative tables to support an N:M association. (That is, ones that can be a
|
126
|
+
# # part of a has_many :through association.) If you want to use different exclusion columns than our defaults
|
127
|
+
# # then this setting resets that list. For instance, here is an override that is useful in the Sakila sample
|
128
|
+
# # database:
|
129
|
+
# Brick.metadata_columns = ['last_update']
|
130
|
+
|
131
|
+
# # A simple DSL is available to allow more user-friendly display of objects. Normally a user object might be shown
|
132
|
+
# # as its first non-metadata column, or if that is not available then something like \"User #45\" where 45 is that
|
133
|
+
# # object's ID. If there is no primary key then even that is not possible, so the object's .to_s method is called.
|
134
|
+
# # To override these defaults and specify exactly what you want shown, such as first names and last names for a
|
135
|
+
# # user, then you can use model_descrips like this, putting expressions with property references in square brackets:
|
136
|
+
# Brick.model_descrips = { 'User' => '[profile.firstname] [profile.lastname]' }
|
64
137
|
|
65
138
|
# # If a default route is not supplied, Brick attempts to find the most \"central\" table and wires up the default
|
66
139
|
# # route to go to the :index action for what would be a controller for that table. You can specify any controller
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-03-
|
11
|
+
date: 2022-03-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|