brick 1.0.3 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff255b32c913dbb0e02d3f197d035faf43dc44afc4041cda3f57e92fc9df893a
4
- data.tar.gz: 763b5579402a7668abf3d266665879643b8d44ed04686a35f2df145b1ddfee72
3
+ metadata.gz: 634669de2074746d62642b8c9108070b78e92b14f7924ac969404b240de2267d
4
+ data.tar.gz: 7614fa456fcaeb6bc68227230088fe3adeed31278241a000f3fb19792b763d26
5
5
  SHA512:
6
- metadata.gz: f513e360dcba221bbd5061eb2ef3ef349a7c18ec6babae32273e877ba5b8cd0071f337f11c63460157afaa55cb0e8eea7dc99d80f64231270fc76218ef84397e
7
- data.tar.gz: eb684612e551f8c884b618985884ad88b5ed827680fb4056f91f6c575fb479d18b92e6d4fa6de222ec24a7991f419723447313780fdbe5b7a0a903561dcb3dd7
6
+ metadata.gz: 3b89ed9c8ffe3c63e098a3f7a8e5a9987db0c00bfe2c917b7c2acefebc30653a4a88d091852b8f8e6128daa310e7c7742d10611acfb0ffe7d06cc6c01da7068e
7
+ data.tar.gz: 98d58b4461b9e85537da640e3940316553d142a341547086ce614f75d9366145c8818beb6e972ac93a77e7209b034eb64a64eb9a078f6afb91fbbd008e3ade69
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
@@ -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 = sectionn / etc
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
@@ -57,14 +61,15 @@ module ActiveRecord
57
61
  wheres = {}
58
62
  rel_joins = []
59
63
  params.each do |k, v|
60
- if (ks = k.split('.')).length > 1
64
+ case (ks = k.split('.')).length
65
+ when 1
66
+ next unless klass._brick_get_fks.include?(k)
67
+ when 2
61
68
  assoc_name = ks.first.to_sym
62
69
  # Make sure it's a good association name and that the model has that column name
63
70
  next unless klass.reflect_on_association(assoc_name)&.klass&.columns&.map(&:name)&.include?(ks.last)
64
71
 
65
72
  rel_joins << assoc_name unless rel_joins.include?(assoc_name)
66
- else
67
- next unless klass._brick_get_fks.include?(k)
68
73
  end
69
74
  wheres[k] = v.split(',')
70
75
  end
@@ -75,17 +80,19 @@ module ActiveRecord
75
80
  end
76
81
  end
77
82
  end
78
- end
79
83
 
80
- # Rails::Application.class_exec do
81
- # alias _brick_initialize! initialize!
82
- # # Run Before initialize make sure our settings
83
- # def initialize!
84
- # # initialization code goes here
85
- # puts "BEFORE1"
86
- # _brick_initialize!
87
- # end
88
- # end
84
+ module Inheritance
85
+ module ClassMethods
86
+ private
87
+
88
+ alias _brick_find_sti_class find_sti_class
89
+ def find_sti_class(type_name)
90
+ ::Brick.sti_models[type_name] = { base: self } unless type_name.blank?
91
+ _brick_find_sti_class(type_name)
92
+ end
93
+ end
94
+ end
95
+ end
89
96
 
90
97
  # Object.class_exec do
91
98
  class Object
@@ -118,6 +125,11 @@ class Object
118
125
  singular_table_name = ActiveSupport::Inflector.underscore(model_name)
119
126
  table_name = ActiveSupport::Inflector.pluralize(singular_table_name)
120
127
 
128
+ # 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
132
+
121
133
  # Maybe, just maybe there's a database table that will satisfy this need
122
134
  if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
123
135
  build_model(model_name, singular_table_name, table_name, relations, matching)
@@ -143,8 +155,9 @@ class Object
143
155
  if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
144
156
  raise NameError.new("Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\".")
145
157
  end
146
- code = +"class #{model_name} < ActiveRecord::Base\n"
147
- built_model = Class.new(ActiveRecord::Base) do |new_model_class|
158
+ base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ActiveRecord::Base
159
+ code = +"class #{model_name} < #{base_model.name}\n"
160
+ built_model = Class.new(base_model) do |new_model_class|
148
161
  Object.const_set(model_name.to_sym, new_model_class)
149
162
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
150
163
  code << " self.table_name = '#{self.table_name = matching}'\n" unless table_name == matching
@@ -180,15 +193,6 @@ class Object
180
193
  code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
181
194
  end
182
195
 
183
- # if relation[:cols].key?('last_update')
184
- # define_method :updated_at do
185
- # last_update
186
- # end
187
- # define_method :'updated_at=' do |val|
188
- # last_update=(val)
189
- # end
190
- # end
191
-
192
196
  fks = relation[:fks] || {}
193
197
  fks.each do |_constraint_name, assoc|
194
198
  assoc_name = assoc[:assoc_name]
@@ -199,21 +203,38 @@ class Object
199
203
  need_class_name = singular_table_name.underscore != assoc_name
200
204
  need_fk = "#{assoc_name}_id" != assoc[:fk]
201
205
  inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], assoc[:inverse])
206
+ if (has_ones = ::Brick.config.has_ones.fetch(assoc[:inverse][:alternate_name].camelize, nil))&.key?(singular_inv_assoc_name = ActiveSupport::Inflector.singularize(inverse_assoc_name))
207
+ inverse_assoc_name = if has_ones[singular_inv_assoc_name]
208
+ need_inverse_of = true
209
+ has_ones[singular_inv_assoc_name]
210
+ else
211
+ singular_inv_assoc_name
212
+ end
213
+ end
202
214
  :belongs_to
203
215
  else
204
216
  # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
205
217
  # Are there multiple foreign keys out to the same table?
206
218
  assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
207
- need_fk = "#{singular_table_name}_id" != assoc[:fk]
219
+ need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
208
220
  # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
209
- :has_many
221
+ if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
222
+ assoc_name = if has_ones[singular_assoc_name]
223
+ need_class_name = true
224
+ has_ones[singular_assoc_name]
225
+ else
226
+ singular_assoc_name
227
+ end
228
+ :has_one
229
+ else
230
+ :has_many
231
+ end
210
232
  end
233
+ # Figure out if we need to specially call out the class_name and/or foreign key
234
+ # (and if either of those then definitely also a specific inverse_of)
211
235
  options[:class_name] = singular_table_name.camelize if need_class_name
212
- # Figure out if we need to specially call out the foreign key
213
- if need_fk # Funky foreign key?
214
- options[:foreign_key] = assoc[:fk].to_sym
215
- end
216
- options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk
236
+ options[:foreign_key] = assoc[:fk].to_sym if need_fk # Funky foreign key?
237
+ options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk || need_inverse_of
217
238
  assoc_name = assoc_name.to_sym
218
239
  code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
219
240
  self.send(macro, assoc_name, **options)
@@ -243,7 +264,7 @@ class Object
243
264
  Object.const_set(class_name.to_sym, new_controller_class)
244
265
 
245
266
  code << " def index\n"
246
- code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect}" : '.all'})\n"
267
+ code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect})" : '.all'}\n"
247
268
  code << " @#{table_name}.brick_where(params)\n"
248
269
  code << " end\n"
249
270
  self.define_method :index do
@@ -288,16 +309,38 @@ end
288
309
  module ActiveRecord::ConnectionHandling
289
310
  alias _brick_establish_connection establish_connection
290
311
  def establish_connection(*args)
291
- x = _brick_establish_connection(*args)
312
+ conn = _brick_establish_connection(*args)
313
+ _brick_reflect_tables
314
+ conn
315
+ end
292
316
 
293
- if (relations = ::Brick.relations).empty?
294
- schema = 'public'
295
- puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
296
- sql = ActiveRecord::Base.send(:sanitize_sql_array, [
317
+ def _brick_reflect_tables
318
+ if (relations = ::Brick.relations).empty?
319
+ # Only for Postgres? (Doesn't work in sqlite3)
320
+ # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
321
+
322
+ case ActiveRecord::Base.connection.adapter_name
323
+ when 'PostgreSQL'
324
+ schema = 'public'
325
+ when 'Mysql2'
326
+ schema = ActiveRecord::Base.connection.current_database
327
+ when 'SQLite'
328
+ sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
329
+ p.name AS column_name, p.type AS data_type,
330
+ CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
331
+ FROM sqlite_master AS m
332
+ INNER JOIN pragma_table_info(m.name) AS p
333
+ WHERE m.name NOT IN ('ar_internal_metadata', 'schema_migrations')
334
+ ORDER BY m.name, p.cid"
335
+ else
336
+ puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
337
+ end
338
+
339
+ sql ||= ActiveRecord::Base.send(:sanitize_sql_array, [
297
340
  "SELECT t.table_name AS relation_name, t.table_type,
298
341
  c.column_name, c.data_type,
299
342
  COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
300
- tc.constraint_type AS const, kcu.constraint_name AS key
343
+ tc.constraint_type AS const, kcu.constraint_name AS \"key\"
301
344
  FROM INFORMATION_SCHEMA.tables AS t
302
345
  LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
303
346
  AND t.table_name = c.table_name
@@ -309,48 +352,88 @@ module ActiveRecord::ConnectionHandling
309
352
  AND kcu.ordinal_position = c.ordinal_position
310
353
  LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
311
354
  ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
355
+ AND kcu.TABLE_NAME = tc.TABLE_NAME
312
356
  AND kcu.CONSTRAINT_NAME = tc.constraint_name
313
357
  WHERE t.table_schema = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')
314
358
  -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
315
359
  AND t.table_name NOT IN ('pg_stat_statements', 'ar_internal_metadata', 'schema_migrations')
316
360
  ORDER BY 1, t.table_type DESC, c.ordinal_position", schema
317
361
  ])
318
- ActiveRecord::Base.connection.execute(sql).each do |r|
319
- # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
320
-
321
- relation = relations[r['relation_name']]
322
- relation[:isView] = true if r['table_type'] == 'VIEW'
323
- col_name = r['column_name']
324
- cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
325
- key = case r['const']
326
- when 'PRIMARY KEY'
327
- relation[:pkey][r['key']] ||= []
328
- when 'UNIQUE'
329
- relation[:ukeys][r['key']] ||= []
330
- # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
331
- # key[r['key']]
332
- end
333
- key << col_name if key
334
- cols[col_name] = [r['data_type'], r['max_length'], r['measures']&.include?(col_name)]
335
- # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
362
+
363
+ measures = []
364
+ case ActiveRecord::Base.connection.adapter_name
365
+ when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
366
+ ActiveRecord::Base.connection.execute(sql).each do |r|
367
+ # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
368
+ relation = relations[(relation_name = r['relation_name'])]
369
+ relation[:isView] = true if r['table_type'] == 'VIEW'
370
+ col_name = r['column_name']
371
+ key = case r['const']
372
+ when 'PRIMARY KEY'
373
+ relation[:pkey][r['key'] || relation_name] ||= []
374
+ when 'UNIQUE'
375
+ relation[:ukeys][r['key'] || "#{relation_name}.#{col_name}"] ||= []
376
+ # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
377
+ # key[r['key']]
378
+ end
379
+ key << col_name if key
380
+ cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
381
+ cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name)]
382
+ # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
383
+ end
384
+ else # MySQL2 acts a little differently, bringing back an array for each row
385
+ ActiveRecord::Base.connection.execute(sql).each do |r|
386
+ # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
387
+ relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
388
+ relation[:isView] = true if r[1] == 'VIEW' # table_type
389
+ col_name = r[2]
390
+ key = case r[5] # constraint type
391
+ when 'PRIMARY KEY'
392
+ # key
393
+ relation[:pkey][r[6] || relation_name] ||= []
394
+ when 'UNIQUE'
395
+ relation[:ukeys][r[6] || "#{relation_name}.#{col_name}"] ||= []
396
+ # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
397
+ # key[r['key']]
398
+ end
399
+ key << col_name if key
400
+ cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
401
+ # 'data_type', 'max_length'
402
+ cols[col_name] = [r[3], r[4], measures&.include?(col_name)]
403
+ # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
404
+ end
336
405
  end
337
406
 
338
- sql = ActiveRecord::Base.send(:sanitize_sql_array, [
339
- "SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME, kcu1.CONSTRAINT_NAME
340
- FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
341
- INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
342
- ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
343
- AND kcu1.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
344
- AND kcu1.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
345
- INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu2
346
- ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
347
- AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
348
- AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
349
- AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
350
- WHERE kcu1.CONSTRAINT_SCHEMA = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')", schema
351
- # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
352
- ])
353
- ActiveRecord::Base.connection.execute(sql).values.each { |fk| ::Brick._add_bt_and_hm(fk, relations) }
407
+ case ActiveRecord::Base.connection.adapter_name
408
+ when 'PostgreSQL', 'Mysql2'
409
+ sql = ActiveRecord::Base.send(:sanitize_sql_array, [
410
+ "SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME
411
+ FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
412
+ INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
413
+ ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
414
+ AND kcu1.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
415
+ AND kcu1.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
416
+ INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu2
417
+ ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
418
+ AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
419
+ AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
420
+ AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
421
+ WHERE kcu1.CONSTRAINT_SCHEMA = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')", schema
422
+ # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
423
+ ])
424
+ when 'SQLite'
425
+ sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
426
+ FROM sqlite_master m
427
+ INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
428
+ ORDER BY m.name, fkl.seq"
429
+ else
430
+ end
431
+ if sql
432
+ ActiveRecord::Base.connection.execute(sql).each do |fk|
433
+ fk = fk.values unless fk.is_a?(Array)
434
+ ::Brick._add_bt_and_hm(fk, relations)
435
+ end
436
+ end
354
437
  end
355
438
 
356
439
  puts "Classes that can be built from tables:"
@@ -361,7 +444,6 @@ module ActiveRecord::ConnectionHandling
361
444
 
362
445
  # relations.keys.each { |k| ActiveSupport::Inflector.singularize(k).camelize.constantize }
363
446
  # Layout table describes permissioned hierarchy throughout
364
- x
365
447
  end
366
448
  end
367
449
 
@@ -5,11 +5,8 @@ module Brick
5
5
  # See http://guides.rubyonrails.org/engines.html
6
6
  class Engine < ::Rails::Engine
7
7
  # paths['app/models'] << 'lib/brick/frameworks/active_record/models'
8
- puts "BEFORE - engine set config"
9
8
  config.brick = ActiveSupport::OrderedOptions.new
10
- # initializer 'brick.initialisation' do |app|
11
9
  ActiveSupport.on_load(:before_initialize) do |app|
12
- puts "BEFORE - engine initialisation"
13
10
  ::Brick.enable_models = app.config.brick.fetch(:enable_models, true)
14
11
  ::Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, true)
15
12
  ::Brick.enable_views = app.config.brick.fetch(:enable_views, true)
@@ -25,83 +22,72 @@ module Brick
25
22
  # Additional references (virtual foreign keys)
26
23
  ::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)
27
24
 
28
- # After we're initialized and before running the rest of stuff, put our configuration in place
29
- ActiveSupport.on_load(:after_initialize) do |xyz|
30
- puts "AFTER - engine initialisation"
31
- # ====================================
32
- # Dynamically create generic templates
33
- # ====================================
34
- if ::Brick.enable_views?
35
- ActionView::LookupContext.class_exec do
36
- alias :_brick_template_exists? :template_exists?
37
- def template_exists?(*args, **options)
38
- unless (is_template_exists = _brick_template_exists?(*args, **options))
39
- # Need to return true if we can fill in the blanks for a missing one
40
- # args will be something like: ["index", ["categories"]]
41
- model = args[1].map(&:camelize).join('::').singularize.constantize
42
- if (
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
- )
49
- instance_variable_set(:@_brick_model, model)
50
- end
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
31
+ # ====================================
32
+ # Dynamically create generic templates
33
+ # ====================================
34
+ if ::Brick.enable_views?
35
+ ActionView::LookupContext.class_exec do
36
+ alias :_brick_template_exists? :template_exists?
37
+ def template_exists?(*args, **options)
38
+ unless (is_template_exists = _brick_template_exists?(*args, **options))
39
+ # Need to return true if we can fill in the blanks for a missing one
40
+ # args will be something like: ["index", ["categories"]]
41
+ model = args[1].map(&:camelize).join('::').singularize.constantize
42
+ if (
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
+ )
49
+ instance_variable_set(:@_brick_model, model)
51
50
  end
52
- is_template_exists
53
51
  end
52
+ is_template_exists
53
+ end
54
54
 
55
- alias :_brick_find_template :find_template
56
- def find_template(*args, **options)
57
- if @_brick_model
58
- model_name = @_brick_model.name
59
- pk = @_brick_model.primary_key
60
- obj_name = model_name.underscore
61
- table_name = model_name.pluralize.underscore
62
- # This gets has_many as well as has_many :through
63
- # %%% weed out ones that don't have an available model to reference
64
- bts, hms = @_brick_model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
65
- case a.macro
66
- when :belongs_to
67
- # Build #brick_descrip if needed
68
- unless a.klass.instance_methods(false).include?(:brick_descrip)
69
- descrip_col = (a.klass.columns.map(&:name) - a.klass._brick_get_fks -
70
- (::Brick.config.metadata_columns || []) -
71
- [a.klass.primary_key]).first&.to_sym
72
- if descrip_col
73
- a.klass.define_method :brick_descrip do
74
- send(descrip_col)
75
- end
76
- end
77
- end
78
-
79
- s.first[a.foreign_key] = [a.name, a.klass]
80
- when :has_many
81
- s.last[a.name] = a
82
- end
83
- s
84
- end
85
- # Weed out has_manys that go to an associative table
86
- associatives = hms.select { |k, v| v.options[:through] }.each_with_object({}) do |hmt, s|
87
- s[hmt.first] = hms.delete(hmt.last.options[:through]) # End up with a hash of HMT names pointing to join-table associations
55
+ alias :_brick_find_template :find_template
56
+ def find_template(*args, **options)
57
+ if @_brick_model
58
+ model_name = @_brick_model.name
59
+ pk = @_brick_model.primary_key
60
+ obj_name = model_name.underscore
61
+ table_name = model_name.pluralize.underscore
62
+ # This gets has_many as well as has_many :through
63
+ # %%% weed out ones that don't have an available model to reference
64
+ bts, hms = ::Brick.get_bts_and_hms(@_brick_model)
65
+ # Weed out has_manys that go to an associative table
66
+ associatives = hms.select { |k, v| v.options[:through] }.each_with_object({}) do |hmt, s|
67
+ s[hmt.first] = hms.delete(hmt.last.options[:through]) # End up with a hash of HMT names pointing to join-table associations
68
+ end
69
+ hms_headers = hms.each_with_object(+'') { |hm, s| s << "<th>H#{hm.last.macro == :has_one ? 'O' : 'M'}#{'T' if hm.last.options[:through]} #{hm.first}</th>\n" }
70
+ hms_columns = hms.each_with_object(+'') do |hm, s|
71
+ hm_fk_name = if hm.last.options[:through]
72
+ associative = associatives[hm.last.name]
73
+ "'#{associative.name}.#{associative.foreign_key}'"
74
+ else
75
+ hm.last.foreign_key
88
76
  end
89
- hms_headers = hms.each_with_object(+'') { |hm, s| s << "<th>HM#{'T' if hm.last.options[:through]} #{hm.first}</th>\n" }
90
- hms_columns = hms.each_with_object(+'') do |hm, s|
91
- hm_fk_name = if hm.last.options[:through]
92
- associative = associatives[hm.last.name]
93
- "'#{associative.name}.#{associative.foreign_key}'"
94
- else
95
- hm.last.foreign_key
96
- end
97
- s << "<td>
98
- <%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm.last.klass.name.underscore.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) %>
77
+ s << if hm.last.macro == :has_many
78
+ "<td>
79
+ <%= 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? %>
99
80
  </td>\n"
100
- end
81
+ else # has_one
82
+ "<td>
83
+ <%= obj = #{obj_name}.#{hm.first}; link_to(obj.brick_descrip, obj) if obj %>
84
+ </td>\n"
85
+ end
86
+ end
101
87
 
102
- inline = case args.first
103
- when 'index'
104
- "<p style=\"color: green\"><%= notice %></p>
88
+ inline = case args.first
89
+ when 'index'
90
+ "<p style=\"color: green\"><%= notice %></p>
105
91
 
106
92
  <h1>#{model_name.pluralize}</h1>
107
93
  <% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
@@ -109,7 +95,7 @@ module Brick
109
95
  <table id=\"#{table_name}\">
110
96
  <tr>
111
97
  <% is_first = true; is_need_id_col = nil
112
- bts = { #{bts.each_with_object([]) { |v, s| s << "#{v.first.inspect} => [#{v.last.first.inspect}, #{v.last.last.name}, #{v.last.last.primary_key.inspect}]"}.join(', ')} }
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(', ')} }
113
99
  @#{table_name}.columns.map(&:name).each do |col| %>
114
100
  <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) %>
115
101
  <th>
@@ -119,7 +105,7 @@ module Brick
119
105
  is_need_id_col = true %>
120
106
  </th><th>
121
107
  <% end %>
122
- BT <%= bt[1].name %>
108
+ BT <%= \"#\{bt.first\}-\" unless bt[1].name.underscore == bt.first.to_s %><%= bt[1].name %>
123
109
  <% else
124
110
  is_first = false %>
125
111
  <%= col %>
@@ -145,9 +131,9 @@ module Brick
145
131
  <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
146
132
  <td>
147
133
  <% if (bt = bts[k]) %>
148
- <%= obj = bt[1].find_by(bt.last => val); link_to obj.brick_descrip, obj %>
134
+ <%= obj = bt[1].find_by(bt.last => val); link_to(obj.brick_descrip, obj) if obj %>
149
135
  <% elsif is_first %>
150
- <%= is_first = false; link_to val, #{obj_name} %>
136
+ <%= is_first = false; link_to val, #{obj_name}_path(#{obj_name}.#{pk}) %>
151
137
  <% else %>
152
138
  <%= val %>
153
139
  <% end %>
@@ -159,66 +145,60 @@ module Brick
159
145
  <% end %>
160
146
  </table>
161
147
 
162
- <%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>
148
+ #{"<hr><%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>" unless @_brick_model.is_view?}
163
149
  "
164
- # "<%= @#{@_brick_model.name.underscore.pluralize}.inspect %>"
165
- when 'show'
166
- "<%= @#{@_brick_model.name.underscore}.inspect %>"
167
- end
168
- # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
169
- keys = options.has_key?(:locals) ? options[:locals].keys : []
170
- handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
171
- ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
172
- else
173
- _brick_find_template(*args, **options)
150
+ when 'show'
151
+ "<%= @#{@_brick_model.name.underscore}.inspect %>"
174
152
  end
153
+ # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
154
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
155
+ handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
156
+ ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
157
+ else
158
+ _brick_find_template(*args, **options)
175
159
  end
176
160
  end
177
161
  end
162
+ end
178
163
 
179
- if ::Brick.enable_routes?
180
- ActionDispatch::Routing::RouteSet.class_exec do
181
- alias _brick_finalize_routeset! finalize!
182
- def finalize!(*args, **options)
183
- unless @finalized
184
- existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
185
- ::Rails.application.routes.append do
186
- # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
187
- # If auto-controllers and auto-models are both enabled then this makes sense:
188
- relations = (::Brick.instance_variable_get(:@relations) || {})[ActiveRecord::Base.connection_pool.object_id] || {}
189
- relations.each do |k, v|
190
- unless existing_controllers.key?(controller_name = k.underscore.pluralize)
191
- options = {}
192
- options[:only] = [:index, :show] if v.key?(:isView)
193
- send(:resources, controller_name.to_sym, **options)
194
- end
164
+ if ::Brick.enable_routes?
165
+ ActionDispatch::Routing::RouteSet.class_exec do
166
+ alias _brick_finalize_routeset! finalize!
167
+ def finalize!(*args, **options)
168
+ unless @finalized
169
+ existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
170
+ ::Rails.application.routes.append do
171
+ # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
172
+ # If auto-controllers and auto-models are both enabled then this makes sense:
173
+ ::Brick.relations.each do |k, v|
174
+ unless existing_controllers.key?(controller_name = k.underscore.pluralize)
175
+ options = {}
176
+ options[:only] = [:index, :show] if v.key?(:isView)
177
+ send(:resources, controller_name.to_sym, **options)
195
178
  end
196
179
  end
197
180
  end
198
- _brick_finalize_routeset!(*args, **options)
199
181
  end
182
+ _brick_finalize_routeset!(*args, **options)
200
183
  end
201
184
  end
185
+ end
202
186
 
203
- # Additional references (virtual foreign keys)
204
- if (ars = ::Brick.config.additional_references)
205
- ars = ars.call if ars.is_a?(Proc)
206
- ars = ars.to_a unless ars.is_a?(Array)
207
- ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
208
- ars.each do |fk|
209
- ::Brick._add_bt_and_hm(fk[0..2])
210
- end
187
+ # Additional references (virtual foreign keys)
188
+ if (ars = ::Brick.config.additional_references)
189
+ ars.each do |fk|
190
+ ::Brick._add_bt_and_hm(fk[0..2])
211
191
  end
192
+ end
212
193
 
213
- # Find associative tables that can be set up for has_many :through
214
- ::Brick.relations.each do |_key, tbl|
215
- tbl_cols = tbl[:cols].keys
216
- fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
217
- # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
218
- # foreign keys then it can act as an associative table and thus be used with has_many :through.
219
- if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - tbl[:pkey].values.first).length.zero?
220
- fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
221
- end
194
+ # Find associative tables that can be set up for has_many :through
195
+ ::Brick.relations.each do |_key, tbl|
196
+ tbl_cols = tbl[:cols].keys
197
+ fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
198
+ # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
199
+ # foreign keys then it can act as an associative table and thus be used with has_many :through.
200
+ if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - (tbl[:pkey].values.first || [])).length.zero?
201
+ fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
222
202
  end
223
203
  end
224
204
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 3
8
+ TINY = 6
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
@@ -76,8 +76,15 @@ 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
- require 'brick/frameworks/rails'
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
84
+ def self.sti_models
85
+ @sti_models ||= {}
86
+ end
87
+
81
88
  class << self
82
89
  # All tables and views (what Postgres calls "relations" including column and foreign key info)
83
90
  def relations
@@ -87,6 +94,30 @@ module Brick
87
94
  (connections[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } })
88
95
  end
89
96
 
97
+ def get_bts_and_hms(model)
98
+ model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
99
+ case a.macro
100
+ 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
+ s.first[a.foreign_key] = [a.name, a.klass]
114
+ when :has_many, :has_one
115
+ s.last[a.name] = a
116
+ end
117
+ s
118
+ end
119
+ end
120
+
90
121
  # Switches Brick auto-models on or off, for all threads
91
122
  # @api public
92
123
  def enable_models=(value)
@@ -156,8 +187,28 @@ module Brick
156
187
 
157
188
  # Additional table associations to use (Think of these as virtual foreign keys perhaps)
158
189
  # @api public
159
- def additional_references=(value)
160
- Brick.config.additional_references = value
190
+ def additional_references=(ars)
191
+ if ars
192
+ ars = ars.call if ars.is_a?(Proc)
193
+ ars = ars.to_a unless ars.is_a?(Array)
194
+ ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
195
+ Brick.config.additional_references = ars
196
+ end
197
+ end
198
+
199
+ # Associations to treat has a has_one
200
+ # @api public
201
+ def has_ones=(hos)
202
+ if hos
203
+ hos = hos.call if hos.is_a?(Proc)
204
+ hos = hos.to_a unless hos.is_a?(Array)
205
+ hos = [hos] unless hos.empty? || hos.first.is_a?(Array)
206
+ # Translate to being nested hashes
207
+ Brick.config.has_ones = hos&.each_with_object(Hash.new { |h, k| h[k] = {} }) do |v, s|
208
+ s[v.first][v[1]] = v[2] if v[1]
209
+ s
210
+ end
211
+ end
161
212
  end
162
213
 
163
214
 
@@ -197,10 +248,7 @@ module Brick
197
248
  end
198
249
  end
199
250
 
200
- require 'brick/extensions'
201
251
  require 'brick/version_number'
202
- # require 'brick/serializers/json'
203
- # require 'brick/serializers/yaml'
204
252
 
205
253
  require 'active_record'
206
254
  # Major compatibility fixes for ActiveRecord < 4.2
@@ -426,18 +474,18 @@ ActiveSupport.on_load(:active_record) do
426
474
  end
427
475
  end
428
476
 
429
- include ::Brick::Extensions
430
-
431
- unless ::Brick::Extensions::IS_AMOEBA
432
- # Add amoeba-compatible support
433
- module ActiveRecord
434
- class Base
435
- def self.amoeba(*args)
436
- puts "Amoeba called from #{name} with #{args.inspect}"
437
- end
438
- end
439
- end
440
- end
477
+ # include ::Brick::Extensions
478
+
479
+ # unless ::Brick::Extensions::IS_AMOEBA
480
+ # # Add amoeba-compatible support
481
+ # module ActiveRecord
482
+ # class Base
483
+ # def self.amoeba(*args)
484
+ # puts "Amoeba called from #{name} with #{args.inspect}"
485
+ # end
486
+ # end
487
+ # end
488
+ # end
441
489
  end
442
490
 
443
491
  # Do this earlier because stuff here gets mixed into JoinDependency::JoinAssociation and AssociationScope
@@ -526,3 +574,5 @@ if ActiveRecord.version < ::Gem::Version.new('5.2')
526
574
  end # module ActiveRecord
527
575
  # rubocop:enable Style/CommentedKeyword
528
576
  end
577
+
578
+ 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.3
4
+ version: 1.0.6
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-14 00:00:00.000000000 Z
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