brick 1.0.2 → 1.0.5
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 +9 -0
- data/lib/brick/extensions.rb +146 -63
- data/lib/brick/frameworks/rails/engine.rb +53 -63
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +64 -18
- data/lib/generators/brick/install_generator.rb +8 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9cd49783dae6701760920fe007cbde7382fd3dbaf5e8f7472d475c0fe8aaf660
|
4
|
+
data.tar.gz: d2330f28e87dde00b1377f6cbca31cbbfe2dd3b2c495f1fbd0aebc0f8a951504
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a3c7ba47d32f3a6ab6c9be838006826f5266efb1349d90ce7d53b5e8f89ea078bdcb699a7db8084176e2de444a27931ce8e0216af666c25704c0568b59ac827
|
7
|
+
data.tar.gz: 41fccf3387315cd54fd596955c3f07760941cd4aa51959dc99f87d0b6fb38de742ad11fe7dabb084a3e72167c340b8aafce838c7704626359ccb41878e202ae2
|
data/lib/brick/config.rb
CHANGED
@@ -65,6 +65,15 @@ module Brick
|
|
65
65
|
@mutex.synchronize { @additional_references = references }
|
66
66
|
end
|
67
67
|
|
68
|
+
# Associations to treat has a has_one
|
69
|
+
def has_ones
|
70
|
+
@mutex.synchronize { @has_ones }
|
71
|
+
end
|
72
|
+
|
73
|
+
def has_ones=(references)
|
74
|
+
@mutex.synchronize { @has_ones = references }
|
75
|
+
end
|
76
|
+
|
68
77
|
def skip_database_views
|
69
78
|
@mutex.synchronize { @skip_database_views }
|
70
79
|
end
|
data/lib/brick/extensions.rb
CHANGED
@@ -26,7 +26,11 @@
|
|
26
26
|
|
27
27
|
# colour coded origins
|
28
28
|
|
29
|
-
# Drag TmfModel#name onto the rows and have it automatically add five columns -- where type=zone / where type =
|
29
|
+
# Drag something like TmfModel#name onto the rows and have it automatically add five columns -- where type=zone / where type = section / etc
|
30
|
+
|
31
|
+
# Support for Postgres / MySQL enums (add enum to model, use model enums to make a drop-down in the UI)
|
32
|
+
|
33
|
+
# Currently quadrupling up routes
|
30
34
|
|
31
35
|
# ==========================================================
|
32
36
|
# Dynamically create model or controller classes when needed
|
@@ -55,13 +59,23 @@ module ActiveRecord
|
|
55
59
|
class Relation
|
56
60
|
def brick_where(params)
|
57
61
|
wheres = {}
|
62
|
+
rel_joins = []
|
58
63
|
params.each do |k, v|
|
59
|
-
|
60
|
-
|
64
|
+
case (ks = k.split('.')).length
|
65
|
+
when 1
|
66
|
+
next unless klass._brick_get_fks.include?(k)
|
67
|
+
when 2
|
68
|
+
assoc_name = ks.first.to_sym
|
69
|
+
# Make sure it's a good association name and that the model has that column name
|
70
|
+
next unless klass.reflect_on_association(assoc_name)&.klass&.columns&.map(&:name)&.include?(ks.last)
|
71
|
+
|
72
|
+
rel_joins << assoc_name unless rel_joins.include?(assoc_name)
|
73
|
+
end
|
61
74
|
wheres[k] = v.split(',')
|
62
75
|
end
|
63
76
|
unless wheres.empty?
|
64
77
|
where!(wheres)
|
78
|
+
joins!(rel_joins) unless rel_joins.empty?
|
65
79
|
wheres # Return the specific parameters that we did use
|
66
80
|
end
|
67
81
|
end
|
@@ -161,15 +175,6 @@ class Object
|
|
161
175
|
code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
|
162
176
|
end
|
163
177
|
|
164
|
-
# if relation[:cols].key?('last_update')
|
165
|
-
# define_method :updated_at do
|
166
|
-
# last_update
|
167
|
-
# end
|
168
|
-
# define_method :'updated_at=' do |val|
|
169
|
-
# last_update=(val)
|
170
|
-
# end
|
171
|
-
# end
|
172
|
-
|
173
178
|
fks = relation[:fks] || {}
|
174
179
|
fks.each do |_constraint_name, assoc|
|
175
180
|
assoc_name = assoc[:assoc_name]
|
@@ -180,21 +185,38 @@ class Object
|
|
180
185
|
need_class_name = singular_table_name.underscore != assoc_name
|
181
186
|
need_fk = "#{assoc_name}_id" != assoc[:fk]
|
182
187
|
inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], assoc[:inverse])
|
188
|
+
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))
|
189
|
+
inverse_assoc_name = if has_ones[singular_inv_assoc_name]
|
190
|
+
need_inverse_of = true
|
191
|
+
has_ones[singular_inv_assoc_name]
|
192
|
+
else
|
193
|
+
singular_inv_assoc_name
|
194
|
+
end
|
195
|
+
end
|
183
196
|
:belongs_to
|
184
197
|
else
|
185
198
|
# need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
|
186
199
|
# Are there multiple foreign keys out to the same table?
|
187
200
|
assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
|
188
|
-
need_fk = "#{
|
201
|
+
need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
|
189
202
|
# fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
|
190
|
-
|
203
|
+
if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
|
204
|
+
assoc_name = if has_ones[singular_assoc_name]
|
205
|
+
need_class_name = true
|
206
|
+
has_ones[singular_assoc_name]
|
207
|
+
else
|
208
|
+
singular_assoc_name
|
209
|
+
end
|
210
|
+
:has_one
|
211
|
+
else
|
212
|
+
:has_many
|
213
|
+
end
|
191
214
|
end
|
215
|
+
# Figure out if we need to specially call out the class_name and/or foreign key
|
216
|
+
# (and if either of those then definitely also a specific inverse_of)
|
192
217
|
options[:class_name] = singular_table_name.camelize if need_class_name
|
193
|
-
|
194
|
-
if
|
195
|
-
options[:foreign_key] = assoc[:fk].to_sym
|
196
|
-
end
|
197
|
-
options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk
|
218
|
+
options[:foreign_key] = assoc[:fk].to_sym if need_fk # Funky foreign key?
|
219
|
+
options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk || need_inverse_of
|
198
220
|
assoc_name = assoc_name.to_sym
|
199
221
|
code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
|
200
222
|
self.send(macro, assoc_name, **options)
|
@@ -224,7 +246,7 @@ class Object
|
|
224
246
|
Object.const_set(class_name.to_sym, new_controller_class)
|
225
247
|
|
226
248
|
code << " def index\n"
|
227
|
-
code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect}" : '.all'}
|
249
|
+
code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect})" : '.all'}\n"
|
228
250
|
code << " @#{table_name}.brick_where(params)\n"
|
229
251
|
code << " end\n"
|
230
252
|
self.define_method :index do
|
@@ -269,16 +291,38 @@ end
|
|
269
291
|
module ActiveRecord::ConnectionHandling
|
270
292
|
alias _brick_establish_connection establish_connection
|
271
293
|
def establish_connection(*args)
|
272
|
-
|
294
|
+
conn = _brick_establish_connection(*args)
|
295
|
+
_brick_reflect_tables
|
296
|
+
conn
|
297
|
+
end
|
298
|
+
|
299
|
+
def _brick_reflect_tables
|
300
|
+
if (relations = ::Brick.relations).empty?
|
301
|
+
# Only for Postgres? (Doesn't work in sqlite3)
|
302
|
+
# puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
303
|
+
|
304
|
+
case ActiveRecord::Base.connection.adapter_name
|
305
|
+
when 'PostgreSQL'
|
306
|
+
schema = 'public'
|
307
|
+
when 'Mysql2'
|
308
|
+
schema = ActiveRecord::Base.connection.current_database
|
309
|
+
when 'SQLite'
|
310
|
+
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
311
|
+
p.name AS column_name, p.type AS data_type,
|
312
|
+
CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
|
313
|
+
FROM sqlite_master AS m
|
314
|
+
INNER JOIN pragma_table_info(m.name) AS p
|
315
|
+
WHERE m.name NOT IN ('ar_internal_metadata', 'schema_migrations')
|
316
|
+
ORDER BY m.name, p.cid"
|
317
|
+
else
|
318
|
+
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
319
|
+
end
|
273
320
|
|
274
|
-
|
275
|
-
schema = 'public'
|
276
|
-
puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
277
|
-
sql = ActiveRecord::Base.send(:sanitize_sql_array, [
|
321
|
+
sql ||= ActiveRecord::Base.send(:sanitize_sql_array, [
|
278
322
|
"SELECT t.table_name AS relation_name, t.table_type,
|
279
323
|
c.column_name, c.data_type,
|
280
324
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
281
|
-
tc.constraint_type AS const, kcu.constraint_name AS key
|
325
|
+
tc.constraint_type AS const, kcu.constraint_name AS \"key\"
|
282
326
|
FROM INFORMATION_SCHEMA.tables AS t
|
283
327
|
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
284
328
|
AND t.table_name = c.table_name
|
@@ -290,59 +334,98 @@ module ActiveRecord::ConnectionHandling
|
|
290
334
|
AND kcu.ordinal_position = c.ordinal_position
|
291
335
|
LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
292
336
|
ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
337
|
+
AND kcu.TABLE_NAME = tc.TABLE_NAME
|
293
338
|
AND kcu.CONSTRAINT_NAME = tc.constraint_name
|
294
339
|
WHERE t.table_schema = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')
|
295
340
|
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
296
341
|
AND t.table_name NOT IN ('pg_stat_statements', 'ar_internal_metadata', 'schema_migrations')
|
297
342
|
ORDER BY 1, t.table_type DESC, c.ordinal_position", schema
|
298
343
|
])
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
344
|
+
|
345
|
+
measures = []
|
346
|
+
case ActiveRecord::Base.connection.adapter_name
|
347
|
+
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
348
|
+
ActiveRecord::Base.connection.execute(sql).each do |r|
|
349
|
+
# next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
|
350
|
+
relation = relations[(relation_name = r['relation_name'])]
|
351
|
+
relation[:isView] = true if r['table_type'] == 'VIEW'
|
352
|
+
col_name = r['column_name']
|
353
|
+
key = case r['const']
|
354
|
+
when 'PRIMARY KEY'
|
355
|
+
relation[:pkey][r['key'] || relation_name] ||= []
|
356
|
+
when 'UNIQUE'
|
357
|
+
relation[:ukeys][r['key'] || "#{relation_name}.#{col_name}"] ||= []
|
358
|
+
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
359
|
+
# key[r['key']]
|
360
|
+
end
|
361
|
+
key << col_name if key
|
362
|
+
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
363
|
+
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name)]
|
364
|
+
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
365
|
+
end
|
366
|
+
else # MySQL2 acts a little differently, bringing back an array for each row
|
367
|
+
ActiveRecord::Base.connection.execute(sql).each do |r|
|
368
|
+
# next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
|
369
|
+
relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
|
370
|
+
relation[:isView] = true if r[1] == 'VIEW' # table_type
|
371
|
+
col_name = r[2]
|
372
|
+
key = case r[5] # constraint type
|
373
|
+
when 'PRIMARY KEY'
|
374
|
+
# key
|
375
|
+
relation[:pkey][r[6] || relation_name] ||= []
|
376
|
+
when 'UNIQUE'
|
377
|
+
relation[:ukeys][r[6] || "#{relation_name}.#{col_name}"] ||= []
|
378
|
+
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
379
|
+
# key[r['key']]
|
380
|
+
end
|
381
|
+
key << col_name if key
|
382
|
+
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
383
|
+
# 'data_type', 'max_length'
|
384
|
+
cols[col_name] = [r[3], r[4], measures&.include?(col_name)]
|
385
|
+
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
386
|
+
end
|
317
387
|
end
|
318
388
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
389
|
+
case ActiveRecord::Base.connection.adapter_name
|
390
|
+
when 'PostgreSQL', 'Mysql2'
|
391
|
+
sql = ActiveRecord::Base.send(:sanitize_sql_array, [
|
392
|
+
"SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME
|
393
|
+
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
394
|
+
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
|
395
|
+
ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
|
396
|
+
AND kcu1.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
397
|
+
AND kcu1.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
398
|
+
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu2
|
399
|
+
ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
|
400
|
+
AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
|
401
|
+
AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
402
|
+
AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
|
403
|
+
WHERE kcu1.CONSTRAINT_SCHEMA = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')", schema
|
404
|
+
# AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
|
405
|
+
])
|
406
|
+
when 'SQLite'
|
407
|
+
sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
408
|
+
FROM sqlite_master m
|
409
|
+
INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
|
410
|
+
ORDER BY m.name, fkl.seq"
|
411
|
+
else
|
412
|
+
end
|
413
|
+
if sql
|
414
|
+
ActiveRecord::Base.connection.execute(sql).each do |fk|
|
415
|
+
fk = fk.values unless fk.is_a?(Array)
|
416
|
+
::Brick._add_bt_and_hm(fk, relations)
|
417
|
+
end
|
418
|
+
end
|
335
419
|
end
|
336
420
|
|
337
|
-
puts "Classes built from tables:"
|
421
|
+
puts "Classes that can be built from tables:"
|
338
422
|
relations.select { |_k, v| !v.key?(:isView) }.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
|
339
|
-
puts "Classes built from views:"
|
423
|
+
puts "Classes that can be built from views:"
|
340
424
|
relations.select { |_k, v| v.key?(:isView) }.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
|
341
425
|
# pp relations; nil
|
342
426
|
|
343
427
|
# relations.keys.each { |k| ActiveSupport::Inflector.singularize(k).camelize.constantize }
|
344
428
|
# Layout table describes permissioned hierarchy throughout
|
345
|
-
x
|
346
429
|
end
|
347
430
|
end
|
348
431
|
|
@@ -6,14 +6,32 @@ module Brick
|
|
6
6
|
class Engine < ::Rails::Engine
|
7
7
|
# paths['app/models'] << 'lib/brick/frameworks/active_record/models'
|
8
8
|
config.brick = ActiveSupport::OrderedOptions.new
|
9
|
-
|
10
|
-
Brick.enable_models = app.config.brick.fetch(:enable_models, true)
|
11
|
-
Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, true)
|
9
|
+
ActiveSupport.on_load(:before_initialize) do |app|
|
10
|
+
::Brick.enable_models = app.config.brick.fetch(:enable_models, true)
|
11
|
+
::Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, true)
|
12
|
+
::Brick.enable_views = app.config.brick.fetch(:enable_views, true)
|
13
|
+
::Brick.enable_routes = app.config.brick.fetch(:enable_routes, true)
|
14
|
+
::Brick.skip_database_views = app.config.brick.fetch(:skip_database_views, false)
|
15
|
+
|
16
|
+
# Specific database tables and views to omit when auto-creating models
|
17
|
+
::Brick.exclude_tables = app.config.brick.fetch(:exclude_tables, [])
|
18
|
+
|
19
|
+
# Columns to treat as being metadata for purposes of identifying associative tables for has_many :through
|
20
|
+
::Brick.metadata_columns = app.config.brick.fetch(:metadata_columns, ['created_at', 'updated_at', 'deleted_at'])
|
12
21
|
|
22
|
+
# Additional references (virtual foreign keys)
|
23
|
+
::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)
|
24
|
+
|
25
|
+
# Has one relationships
|
26
|
+
::Brick.has_ones = app.config.brick.fetch(:has_ones, nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
# After we're initialized and before running the rest of stuff, put our configuration in place
|
30
|
+
ActiveSupport.on_load(:after_initialize) do
|
13
31
|
# ====================================
|
14
32
|
# Dynamically create generic templates
|
15
33
|
# ====================================
|
16
|
-
if
|
34
|
+
if ::Brick.enable_views?
|
17
35
|
ActionView::LookupContext.class_exec do
|
18
36
|
alias :_brick_template_exists? :template_exists?
|
19
37
|
def template_exists?(*args, **options)
|
@@ -22,12 +40,12 @@ module Brick
|
|
22
40
|
# args will be something like: ["index", ["categories"]]
|
23
41
|
model = args[1].map(&:camelize).join('::').singularize.constantize
|
24
42
|
if (
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
43
|
+
is_template_exists = model && (
|
44
|
+
['index', 'show'].include?(args.first) || # Everything has index and show
|
45
|
+
# Only CRU stuff has create / update / destroy
|
46
|
+
(!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
|
47
|
+
)
|
48
|
+
)
|
31
49
|
instance_variable_set(:@_brick_model, model)
|
32
50
|
end
|
33
51
|
end
|
@@ -43,34 +61,21 @@ module Brick
|
|
43
61
|
table_name = model_name.pluralize.underscore
|
44
62
|
# This gets has_many as well as has_many :through
|
45
63
|
# %%% weed out ones that don't have an available model to reference
|
46
|
-
bts, hms = @_brick_model
|
47
|
-
case a.macro
|
48
|
-
when :belongs_to
|
49
|
-
# Build #brick_descrip if needed
|
50
|
-
unless a.klass.instance_methods(false).include?(:brick_descrip)
|
51
|
-
descrip_col = (a.klass.columns.map(&:name) - a.klass._brick_get_fks -
|
52
|
-
(::Brick.config.metadata_columns || []) -
|
53
|
-
[a.klass.primary_key]).first&.to_sym
|
54
|
-
if descrip_col
|
55
|
-
a.klass.define_method :brick_descrip do
|
56
|
-
send(descrip_col)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
s.first[a.foreign_key] = [a.name, a.klass]
|
62
|
-
when :has_many
|
63
|
-
s.last[a.name] = a
|
64
|
-
end
|
65
|
-
s
|
66
|
-
end
|
64
|
+
bts, hms = ::Brick.get_bts_and_hms(@_brick_model)
|
67
65
|
# Weed out has_manys that go to an associative table
|
68
|
-
hms.select { |k, v| v.options[:through] }.
|
69
|
-
|
70
|
-
|
66
|
+
associatives = hms.select { |k, v| v.options[:through] }.each_with_object({}) do |hmt, s|
|
67
|
+
s[hmt.first] = hms.delete(hmt.last.options[:through]) # End up with a hash of HMT names pointing to join-table associations
|
68
|
+
end
|
69
|
+
hms_headers = hms.each_with_object(+'') { |hm, s| s << "<th>HM#{'T' if hm.last.options[:through]} #{hm.first}</th>\n" }
|
71
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
|
72
77
|
s << "<td>
|
73
|
-
<%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm.last.klass.name.underscore.pluralize}_path({ #{
|
78
|
+
<%= 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? %>
|
74
79
|
</td>\n"
|
75
80
|
end
|
76
81
|
|
@@ -94,7 +99,7 @@ module Brick
|
|
94
99
|
is_need_id_col = true %>
|
95
100
|
</th><th>
|
96
101
|
<% end %>
|
97
|
-
BT <%= bt[1].name %>
|
102
|
+
BT <%= \"#\{bt.first\}-\" unless bt[1].name.underscore == bt.first.to_s %><%= bt[1].name %>
|
98
103
|
<% else
|
99
104
|
is_first = false %>
|
100
105
|
<%= col %>
|
@@ -104,26 +109,26 @@ module Brick
|
|
104
109
|
<% if is_first # STILL haven't been able to write a first non-key / non-metadata column?
|
105
110
|
is_first = false
|
106
111
|
is_need_id_col = true %>
|
107
|
-
<
|
112
|
+
<th></th>
|
108
113
|
<% end %>
|
109
114
|
#{hms_headers}
|
110
115
|
</tr>
|
111
116
|
|
112
117
|
<% @#{table_name}.each do |#{obj_name}| %>
|
113
118
|
<tr>
|
114
|
-
<%
|
115
|
-
|
119
|
+
<% is_first = true
|
120
|
+
if is_need_id_col
|
121
|
+
is_first = false %>
|
122
|
+
<td><%= link_to \"#\{#{obj_name}.class.name\} ##\{#{obj_name}.id\}\", #{obj_name} %></td>
|
116
123
|
<% end %>
|
117
|
-
<%
|
124
|
+
<% #{obj_name}.attributes.each do |k, val| %>
|
118
125
|
<% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
|
126
|
+
<td>
|
119
127
|
<% if (bt = bts[k]) %>
|
120
|
-
<td>
|
121
128
|
<%= obj = bt[1].find_by(bt.last => val); link_to obj.brick_descrip, obj %>
|
122
129
|
<% elsif is_first %>
|
123
|
-
<td>
|
124
130
|
<%= is_first = false; link_to val, #{obj_name} %>
|
125
131
|
<% else %>
|
126
|
-
<td>
|
127
132
|
<%= val %>
|
128
133
|
<% end %>
|
129
134
|
</td>
|
@@ -134,13 +139,11 @@ module Brick
|
|
134
139
|
<% end %>
|
135
140
|
</table>
|
136
141
|
|
137
|
-
|
142
|
+
#{"<hr><%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>" unless @_brick_model.is_view?}
|
138
143
|
"
|
139
|
-
# "<%= @#{@_brick_model.name.underscore.pluralize}.inspect %>"
|
140
144
|
when 'show'
|
141
145
|
"<%= @#{@_brick_model.name.underscore}.inspect %>"
|
142
146
|
end
|
143
|
-
puts inline
|
144
147
|
# As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
|
145
148
|
keys = options.has_key?(:locals) ? options[:locals].keys : []
|
146
149
|
handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
|
@@ -152,7 +155,7 @@ puts inline
|
|
152
155
|
end
|
153
156
|
end
|
154
157
|
|
155
|
-
if
|
158
|
+
if ::Brick.enable_routes?
|
156
159
|
ActionDispatch::Routing::RouteSet.class_exec do
|
157
160
|
alias _brick_finalize_routeset! finalize!
|
158
161
|
def finalize!(*args, **options)
|
@@ -161,8 +164,7 @@ puts inline
|
|
161
164
|
::Rails.application.routes.append do
|
162
165
|
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
163
166
|
# If auto-controllers and auto-models are both enabled then this makes sense:
|
164
|
-
|
165
|
-
relations.each do |k, v|
|
167
|
+
::Brick.relations.each do |k, v|
|
166
168
|
unless existing_controllers.key?(controller_name = k.underscore.pluralize)
|
167
169
|
options = {}
|
168
170
|
options[:only] = [:index, :show] if v.key?(:isView)
|
@@ -176,20 +178,8 @@ puts inline
|
|
176
178
|
end
|
177
179
|
end
|
178
180
|
|
179
|
-
# Do not consider database views when auto-creating models
|
180
|
-
::Brick.skip_database_views = app.config.brick.fetch(:skip_database_views, false)
|
181
|
-
|
182
|
-
# Specific database tables and views to omit when auto-creating models
|
183
|
-
::Brick.exclude_tables = app.config.brick.fetch(:exclude_tables, [])
|
184
|
-
|
185
|
-
# Columns to treat as being metadata for purposes of identifying associative tables for has_many :through
|
186
|
-
::Brick.metadata_columns = app.config.brick.fetch(:metadata_columns, ['created_at', 'updated_at', 'deleted_at'])
|
187
|
-
|
188
181
|
# Additional references (virtual foreign keys)
|
189
|
-
if (ars =
|
190
|
-
ars = ars.call if ars.is_a?(Proc)
|
191
|
-
ars = ars.to_a unless ars.is_a?(Array)
|
192
|
-
ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
|
182
|
+
if (ars = ::Brick.config.additional_references)
|
193
183
|
ars.each do |fk|
|
194
184
|
::Brick._add_bt_and_hm(fk[0..2])
|
195
185
|
end
|
@@ -201,7 +191,7 @@ puts inline
|
|
201
191
|
fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
|
202
192
|
# Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
|
203
193
|
# foreign keys then it can act as an associative table and thus be used with has_many :through.
|
204
|
-
if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - tbl[:pkey].values.first).length.zero?
|
194
|
+
if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - (tbl[:pkey].values.first || [])).length.zero?
|
205
195
|
fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
|
206
196
|
end
|
207
197
|
end
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -76,7 +76,10 @@ end
|
|
76
76
|
# is first established), and then automatically creates models, controllers, views,
|
77
77
|
# and routes based on those available relations.
|
78
78
|
require 'brick/config'
|
79
|
-
|
79
|
+
if Gem::Specification.all_names.any? { |g| g.start_with?('rails-') }
|
80
|
+
require 'rails'
|
81
|
+
require 'brick/frameworks/rails'
|
82
|
+
end
|
80
83
|
module Brick
|
81
84
|
class << self
|
82
85
|
# All tables and views (what Postgres calls "relations" including column and foreign key info)
|
@@ -87,6 +90,30 @@ module Brick
|
|
87
90
|
(connections[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } })
|
88
91
|
end
|
89
92
|
|
93
|
+
def get_bts_and_hms(model)
|
94
|
+
model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
|
95
|
+
case a.macro
|
96
|
+
when :belongs_to
|
97
|
+
# Build #brick_descrip if needed
|
98
|
+
if a.klass.instance_methods(false).exclude?(:brick_descrip)
|
99
|
+
descrip_col = (a.klass.columns.map(&:name) - a.klass._brick_get_fks -
|
100
|
+
(::Brick.config.metadata_columns || []) -
|
101
|
+
[a.klass.primary_key]).first&.to_sym
|
102
|
+
if descrip_col
|
103
|
+
a.klass.define_method :brick_descrip do
|
104
|
+
send(descrip_col)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
s.first[a.foreign_key] = [a.name, a.klass]
|
110
|
+
when :has_many
|
111
|
+
s.last[a.name] = a
|
112
|
+
end
|
113
|
+
s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
90
117
|
# Switches Brick auto-models on or off, for all threads
|
91
118
|
# @api public
|
92
119
|
def enable_models=(value)
|
@@ -156,8 +183,28 @@ module Brick
|
|
156
183
|
|
157
184
|
# Additional table associations to use (Think of these as virtual foreign keys perhaps)
|
158
185
|
# @api public
|
159
|
-
def additional_references=(
|
160
|
-
|
186
|
+
def additional_references=(ars)
|
187
|
+
if ars
|
188
|
+
ars = ars.call if ars.is_a?(Proc)
|
189
|
+
ars = ars.to_a unless ars.is_a?(Array)
|
190
|
+
ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
|
191
|
+
Brick.config.additional_references = ars
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Associations to treat has a has_one
|
196
|
+
# @api public
|
197
|
+
def has_ones=(hos)
|
198
|
+
if hos
|
199
|
+
hos = hos.call if hos.is_a?(Proc)
|
200
|
+
hos = hos.to_a unless hos.is_a?(Array)
|
201
|
+
hos = [hos] unless hos.empty? || hos.first.is_a?(Array)
|
202
|
+
# Translate to being nested hashes
|
203
|
+
Brick.config.has_ones = hos&.each_with_object(Hash.new { |h, k| h[k] = {} }) do |v, s|
|
204
|
+
s[v.first][v[1]] = v[2] if v[1]
|
205
|
+
s
|
206
|
+
end
|
207
|
+
end
|
161
208
|
end
|
162
209
|
|
163
210
|
|
@@ -197,10 +244,7 @@ module Brick
|
|
197
244
|
end
|
198
245
|
end
|
199
246
|
|
200
|
-
require 'brick/extensions'
|
201
247
|
require 'brick/version_number'
|
202
|
-
# require 'brick/serializers/json'
|
203
|
-
# require 'brick/serializers/yaml'
|
204
248
|
|
205
249
|
require 'active_record'
|
206
250
|
# Major compatibility fixes for ActiveRecord < 4.2
|
@@ -426,18 +470,18 @@ ActiveSupport.on_load(:active_record) do
|
|
426
470
|
end
|
427
471
|
end
|
428
472
|
|
429
|
-
include ::Brick::Extensions
|
430
|
-
|
431
|
-
unless ::Brick::Extensions::IS_AMOEBA
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
end
|
473
|
+
# include ::Brick::Extensions
|
474
|
+
|
475
|
+
# unless ::Brick::Extensions::IS_AMOEBA
|
476
|
+
# # Add amoeba-compatible support
|
477
|
+
# module ActiveRecord
|
478
|
+
# class Base
|
479
|
+
# def self.amoeba(*args)
|
480
|
+
# puts "Amoeba called from #{name} with #{args.inspect}"
|
481
|
+
# end
|
482
|
+
# end
|
483
|
+
# end
|
484
|
+
# end
|
441
485
|
end
|
442
486
|
|
443
487
|
# Do this earlier because stuff here gets mixed into JoinDependency::JoinAssociation and AssociationScope
|
@@ -526,3 +570,5 @@ if ActiveRecord.version < ::Gem::Version.new('5.2')
|
|
526
570
|
end # module ActiveRecord
|
527
571
|
# rubocop:enable Style/CommentedKeyword
|
528
572
|
end
|
573
|
+
|
574
|
+
require 'brick/extensions'
|
@@ -48,6 +48,14 @@ module Brick
|
|
48
48
|
# Brick.additional_references = [['orders', 'customer_id', 'customer'],
|
49
49
|
# ['customer', 'region_id', 'regions']]
|
50
50
|
|
51
|
+
# # By default primary tables involved in a foreign key relationship will indicate a \"has_many\" relationship pointing
|
52
|
+
# # back to the foreign table. In order to represent a \"has_one\" association instead, an override can be provided
|
53
|
+
# # using the primary model name and the association name which you instead want to have treated as a \"has_one\":
|
54
|
+
# Brick.has_ones = [['User', 'user_profile']]
|
55
|
+
# # If you want to use an alternate name for the \"has_one\", such as in the case above calling the association \"profile\"
|
56
|
+
# # instead of \"user_profile\", then apply that as a third parameter like this:
|
57
|
+
# Brick.has_ones = [['User', 'user_profile', 'profile']]
|
58
|
+
|
51
59
|
# # We normally don't consider the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\" to count when
|
52
60
|
# # finding tables which can serve as associative tables in an N:M association. That is, ones that can be a
|
53
61
|
# # part of a has_many :through association. If you want to use different exclusion columns than our defaults
|
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.5
|
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-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -156,6 +156,20 @@ dependencies:
|
|
156
156
|
- - "~>"
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: 1.42.0
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: mysql2
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0.5'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0.5'
|
159
173
|
- !ruby/object:Gem::Dependency
|
160
174
|
name: pg
|
161
175
|
requirement: !ruby/object:Gem::Requirement
|