brick 1.0.48 → 1.0.51
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 +24 -0
- data/lib/brick/extensions.rb +202 -86
- data/lib/brick/frameworks/rails/engine.rb +40 -15
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +8 -3
- data/lib/generators/brick/install_generator.rb +46 -2
- 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: 9d0c2c69187f4d9b8eaa4a22777d3e517546bf355316012bf3e333064f8939b7
|
4
|
+
data.tar.gz: 33fcb2f6ca4373eba2db3a2891de392d9ef71ea693687328f72d23837cabdc46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7136cb61d19325c1916e9d2e8e6cd7ed6c1d6838c77a216c5275bf4cc0d3540eeabf5caae152b49b9f5cf0d5ea7307bebd98f2fdbc03e651310c1cfc6fbec436
|
7
|
+
data.tar.gz: 81007bc391e9871ab0bbe80fc5aaa440da2685e1abe16d65d09419e31e93529232757db173cf97ba78ad3e719af760cac2d83b57cb7d97fa488399d93d3d2f76
|
data/lib/brick/config.rb
CHANGED
@@ -190,6 +190,30 @@ module Brick
|
|
190
190
|
@mutex.synchronize { @table_name_prefixes = value }
|
191
191
|
end
|
192
192
|
|
193
|
+
def order
|
194
|
+
@mutex.synchronize { @order || {} }
|
195
|
+
end
|
196
|
+
|
197
|
+
# Get something like:
|
198
|
+
# Override how code sorts with:
|
199
|
+
# { 'on_call_list' => { code: "ORDER BY STRING_TO_ARRAY(code, '.')::int[]" } }
|
200
|
+
# Specify default thing to order_by with:
|
201
|
+
# { 'on_call_list' => { _brick_default: [:last_name, :first_name] } }
|
202
|
+
# { 'on_call_list' => { _brick_default: :sequence } }
|
203
|
+
def order=(orders)
|
204
|
+
@mutex.synchronize do
|
205
|
+
case (brick_default = orders.fetch(:_brick_default, nil))
|
206
|
+
when NilClass
|
207
|
+
orders[:_brick_default] = orders.keys.reject { |k| k == :_brick_default }.first
|
208
|
+
when String
|
209
|
+
orders[:_brick_default] = [brick_default.to_sym]
|
210
|
+
when Symbol
|
211
|
+
orders[:_brick_default] = [brick_default]
|
212
|
+
end
|
213
|
+
@order = orders
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
193
217
|
def metadata_columns
|
194
218
|
@mutex.synchronize { @metadata_columns }
|
195
219
|
end
|
data/lib/brick/extensions.rb
CHANGED
@@ -221,6 +221,68 @@ module ActiveRecord
|
|
221
221
|
template
|
222
222
|
end
|
223
223
|
|
224
|
+
class << self
|
225
|
+
# belongs_to DSL descriptions
|
226
|
+
def _br_bt_descrip
|
227
|
+
@_br_bt_descrip ||= {}
|
228
|
+
end
|
229
|
+
# has_many count definitions
|
230
|
+
def _br_hm_counts
|
231
|
+
@_br_hm_counts ||= {}
|
232
|
+
end
|
233
|
+
# has_many :through associative tables
|
234
|
+
def _br_associatives
|
235
|
+
@_br_associatives ||= {}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Search for BT, HM, and HMT DSL stuff
|
240
|
+
def self._brick_calculate_bts_hms(translations, join_array)
|
241
|
+
bts, hms, associatives = ::Brick.get_bts_and_hms(self)
|
242
|
+
bts.each do |_k, bt|
|
243
|
+
next if bt[2] # Polymorphic?
|
244
|
+
|
245
|
+
# join_array will receive this relation name when calling #brick_parse_dsl
|
246
|
+
_br_bt_descrip[bt.first] = if bt[1].is_a?(Array)
|
247
|
+
bt[1].each_with_object({}) { |bt_class, s| s[bt_class] = bt_class.brick_parse_dsl(join_array, bt.first, translations, true) }
|
248
|
+
else
|
249
|
+
{ bt.last => bt[1].brick_parse_dsl(join_array, bt.first, translations) }
|
250
|
+
end
|
251
|
+
end
|
252
|
+
skip_klass_hms = ::Brick.config.skip_index_hms[self.name] || {}
|
253
|
+
hms.each do |k, hm|
|
254
|
+
next if skip_klass_hms.key?(k)
|
255
|
+
|
256
|
+
if hm.macro == :has_one
|
257
|
+
# For our purposes a :has_one is similar enough to a :belongs_to that we can just join forces
|
258
|
+
_br_bt_descrip[k] = { hm.klass => hm.klass.brick_parse_dsl(join_array, k, translations) }
|
259
|
+
else # Standard :has_many
|
260
|
+
_br_hm_counts[k] = hm
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def self._brick_calculate_ordering(ordering, is_do_txt = true)
|
266
|
+
quoted_table_name = table_name.split('.').map { |x| "\"#{x}\"" }.join('.')
|
267
|
+
order_by_txt = [] if is_do_txt
|
268
|
+
ordering = [ordering] if ordering && !ordering.is_a?(Array)
|
269
|
+
order_by = ordering&.map do |ord_part| # %%% If a term is also used as an eqi-condition in the WHERE clause, it can be omitted from ORDER BY
|
270
|
+
case ord_part
|
271
|
+
when String
|
272
|
+
ord_expr = ord_part.gsub('^^^', quoted_table_name)
|
273
|
+
order_by_txt&.<<("Arel.sql(#{ord_expr})")
|
274
|
+
Arel.sql(ord_expr)
|
275
|
+
else # Expecting only Symbol
|
276
|
+
ord_part = "_br_#{ord_part}_ct" if _br_hm_counts.key?(ord_part)
|
277
|
+
# Retain any reference to a bt_descrip as being a symbol
|
278
|
+
# Was: "#{quoted_table_name}.\"#{ord_part}\""
|
279
|
+
order_by_txt&.<<(_br_bt_descrip.key?(ord_part) ? ord_part : ord_part.inspect)
|
280
|
+
ord_part
|
281
|
+
end
|
282
|
+
end
|
283
|
+
[order_by, order_by_txt]
|
284
|
+
end
|
285
|
+
|
224
286
|
private
|
225
287
|
|
226
288
|
def self._brick_get_fks
|
@@ -310,16 +372,15 @@ module ActiveRecord
|
|
310
372
|
end
|
311
373
|
end
|
312
374
|
|
313
|
-
def brick_select(params, selects = nil,
|
314
|
-
# , is_add_bts, is_add_hms
|
315
|
-
)
|
316
|
-
is_add_bts = is_add_hms = true
|
375
|
+
def brick_select(params, selects = nil, order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
|
317
376
|
is_distinct = nil
|
318
377
|
wheres = {}
|
319
378
|
params.each do |k, v|
|
379
|
+
next if ['_brick_schema', '_brick_order'].include?(k)
|
380
|
+
|
320
381
|
case (ks = k.split('.')).length
|
321
382
|
when 1
|
322
|
-
next unless klass._brick_get_fks.include?(k)
|
383
|
+
next unless klass.column_names.any?(k) || klass._brick_get_fks.include?(k)
|
323
384
|
when 2
|
324
385
|
assoc_name = ks.first.to_sym
|
325
386
|
# Make sure it's a good association name and that the model has that column name
|
@@ -345,33 +406,6 @@ module ActiveRecord
|
|
345
406
|
end
|
346
407
|
end
|
347
408
|
|
348
|
-
# Search for BT, HM, and HMT DSL stuff
|
349
|
-
translations = {}
|
350
|
-
if is_add_bts || is_add_hms
|
351
|
-
bts, hms, associatives = ::Brick.get_bts_and_hms(klass)
|
352
|
-
bts.each do |_k, bt|
|
353
|
-
next if bt[2] # Polymorphic?
|
354
|
-
|
355
|
-
# join_array will receive this relation name when calling #brick_parse_dsl
|
356
|
-
bt_descrip[bt.first] = if bt[1].is_a?(Array)
|
357
|
-
bt[1].each_with_object({}) { |bt_class, s| s[bt_class] = bt_class.brick_parse_dsl(join_array, bt.first, translations, true) }
|
358
|
-
else
|
359
|
-
{ bt.last => bt[1].brick_parse_dsl(join_array, bt.first, translations) }
|
360
|
-
end
|
361
|
-
end
|
362
|
-
skip_klass_hms = ::Brick.config.skip_index_hms[klass.name] || {}
|
363
|
-
hms.each do |k, hm|
|
364
|
-
next if skip_klass_hms.key?(k)
|
365
|
-
|
366
|
-
if hm.macro == :has_one
|
367
|
-
# For our purposes a :has_one is similar enough to a :belongs_to that we can just join forces
|
368
|
-
bt_descrip[k] = { hm.klass => hm.klass.brick_parse_dsl(join_array, k, translations) }
|
369
|
-
else # Standard :has_many
|
370
|
-
hm_counts[k] = hm
|
371
|
-
end
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
409
|
if join_array.present?
|
376
410
|
left_outer_joins!(join_array)
|
377
411
|
# Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
|
@@ -380,7 +414,8 @@ module ActiveRecord
|
|
380
414
|
chains = rel_dupe._brick_chains
|
381
415
|
id_for_tables = Hash.new { |h, k| h[k] = [] }
|
382
416
|
field_tbl_names = Hash.new { |h, k| h[k] = {} }
|
383
|
-
|
417
|
+
used_col_aliases = {} # Used to make sure there is not a name clash
|
418
|
+
bt_columns = klass._br_bt_descrip.each_with_object([]) do |v, s|
|
384
419
|
v.last.each do |k1, v1| # k1 is class, v1 is array of columns to snag
|
385
420
|
next if chains[k1].nil?
|
386
421
|
|
@@ -391,7 +426,12 @@ module ActiveRecord
|
|
391
426
|
|
392
427
|
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
393
428
|
is_xml = is_distinct && Brick.relations[sel_col.first.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
|
394
|
-
|
429
|
+
# If it's not unique then also include the belongs_to association name before the column name
|
430
|
+
if used_col_aliases.key?(col_alias = "_brfk_#{v.first}__#{sel_col.last}")
|
431
|
+
col_alias = "_brfk_#{v.first}__#{v1[idx][-2..-1].map(&:to_s).join('__')}"
|
432
|
+
end
|
433
|
+
selects << "\"#{field_tbl_name}\".\"#{sel_col.last}\"#{'::text' if is_xml} AS \"#{col_alias}\""
|
434
|
+
used_col_aliases[col_alias] = nil
|
395
435
|
v1[idx] << col_alias
|
396
436
|
end
|
397
437
|
|
@@ -416,11 +456,10 @@ module ActiveRecord
|
|
416
456
|
end
|
417
457
|
end
|
418
458
|
# Add derived table JOIN for the has_many counts
|
419
|
-
|
459
|
+
klass._br_hm_counts.each do |k, hm|
|
420
460
|
associative = nil
|
421
461
|
count_column = if hm.options[:through]
|
422
|
-
fk_col = (associative =
|
423
|
-
hm.foreign_key if fk_col
|
462
|
+
hm.foreign_key if (fk_col = (associative = klass._br_associatives&.[](hm.name))&.foreign_key)
|
424
463
|
else
|
425
464
|
fk_col = hm.foreign_key
|
426
465
|
poly_type = hm.inverse_of.foreign_type if hm.options.key?(:as)
|
@@ -450,6 +489,25 @@ JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}
|
|
450
489
|
joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
|
451
490
|
end
|
452
491
|
where!(wheres) unless wheres.empty?
|
492
|
+
# Must parse the order_by and see if there are any symbols which refer to BT associations
|
493
|
+
# as they must be expanded to find the corresponding _br_model__column naming for each.
|
494
|
+
if order_by.present?
|
495
|
+
final_order_by = *order_by.each_with_object([]) do |v, s|
|
496
|
+
if v.is_a?(Symbol)
|
497
|
+
# Add the ordered series of columns derived from the BT based on its DSL
|
498
|
+
if (bt_cols = klass._br_bt_descrip[v])
|
499
|
+
bt_cols.values.each do |v1|
|
500
|
+
v1.each { |v2| s << v2.last if v2.length > 1 }
|
501
|
+
end
|
502
|
+
else
|
503
|
+
s << v
|
504
|
+
end
|
505
|
+
else # String stuff just comes straight through
|
506
|
+
s << v
|
507
|
+
end
|
508
|
+
end
|
509
|
+
order!(*final_order_by)
|
510
|
+
end
|
453
511
|
limit!(1000) # Don't want to get too carried away just yet
|
454
512
|
wheres unless wheres.empty? # Return the specific parameters that we did use
|
455
513
|
end
|
@@ -489,12 +547,23 @@ JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}
|
|
489
547
|
this_module.const_set(module_name.to_sym, Module.new)
|
490
548
|
end
|
491
549
|
end
|
492
|
-
|
493
|
-
this_module.
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
550
|
+
begin
|
551
|
+
if this_module.const_defined?(class_name = module_prefixes.last.to_sym)
|
552
|
+
this_module.const_get(class_name)
|
553
|
+
else
|
554
|
+
# Build STI subclass and place it into the namespace module
|
555
|
+
this_module.const_set(class_name, klass = Class.new(self))
|
556
|
+
klass
|
557
|
+
end
|
558
|
+
rescue NameError => err
|
559
|
+
if column_names.include?(inheritance_column)
|
560
|
+
puts "Table #{table_name} has column #{inheritance_column} which ActiveRecord expects to use as its special inheritance column."
|
561
|
+
puts "Unfortunately the value \"#{type_name}\" does not seem to refer to a valid type name, greatly confusing matters. If that column is intended to be used for data and not STI, consider putting this line into your Brick initializer so that only for this table that column will not clash with ActiveRecord:"
|
562
|
+
puts " Brick.sti_type_column = { 'rails_#{inheritance_column}' => ['#{table_name}'] }"
|
563
|
+
self
|
564
|
+
else
|
565
|
+
raise
|
566
|
+
end
|
498
567
|
end
|
499
568
|
end
|
500
569
|
end
|
@@ -545,6 +614,8 @@ Module.class_exec do
|
|
545
614
|
return possible
|
546
615
|
end
|
547
616
|
class_name = args.first.to_s
|
617
|
+
# self.name is nil when a model name is requested in an .erb file
|
618
|
+
base_module = (self < ActiveRecord::Migration || !self.name) ? Object : self
|
548
619
|
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
549
620
|
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
550
621
|
# that is, checking #qualified_name_for with: from_mod, const_name
|
@@ -552,14 +623,17 @@ Module.class_exec do
|
|
552
623
|
# path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
|
553
624
|
# return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
|
554
625
|
# If the file really exists, go and snag it:
|
555
|
-
if
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
return
|
560
|
-
|
561
|
-
|
562
|
-
|
626
|
+
if ActiveSupport::Dependencies.search_for_file(class_name.underscore)
|
627
|
+
return base_module._brick_const_missing(*args)
|
628
|
+
# elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
|
629
|
+
# my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
|
630
|
+
# return my_const
|
631
|
+
else
|
632
|
+
filepath = base_module.name&.split('::')&.[](0..-2) unless base_module == Object
|
633
|
+
filepath = ((filepath || []) + [class_name]).join('/').underscore + '.rb'
|
634
|
+
if ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
|
635
|
+
return base_module._brick_const_missing(*args)
|
636
|
+
end
|
563
637
|
end
|
564
638
|
|
565
639
|
relations = ::Brick.relations
|
@@ -578,7 +652,7 @@ Module.class_exec do
|
|
578
652
|
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
579
653
|
end
|
580
654
|
elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
|
581
|
-
|
655
|
+
base_module == Object && # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing
|
582
656
|
(schema_name = [(singular_table_name = class_name.underscore),
|
583
657
|
(table_name = singular_table_name.pluralize),
|
584
658
|
class_name,
|
@@ -586,7 +660,7 @@ Module.class_exec do
|
|
586
660
|
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name))
|
587
661
|
# Build out a module for the schema if it's namespaced
|
588
662
|
# schema_name = schema_name.camelize
|
589
|
-
|
663
|
+
base_module.const_set(schema_name.to_sym, (built_module = Module.new))
|
590
664
|
|
591
665
|
[built_module, "module #{schema_name}; end\n"]
|
592
666
|
# # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
|
@@ -596,8 +670,8 @@ Module.class_exec do
|
|
596
670
|
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
597
671
|
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
598
672
|
|
599
|
-
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{
|
600
|
-
|
673
|
+
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
674
|
+
base_module != Object # ... or otherwise already in some namespace?
|
601
675
|
schema_name = [(singular_schema_name = name.underscore),
|
602
676
|
(schema_name = singular_schema_name.pluralize),
|
603
677
|
name,
|
@@ -610,7 +684,7 @@ Module.class_exec do
|
|
610
684
|
if base_model
|
611
685
|
schema_name = name.underscore # For the auto-STI namespace models
|
612
686
|
table_name = base_model.table_name
|
613
|
-
Object.send(:build_model,
|
687
|
+
Object.send(:build_model, base_module, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
|
614
688
|
else
|
615
689
|
# Adjust for STI if we know of a base model for the requested model name
|
616
690
|
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
@@ -635,15 +709,15 @@ Module.class_exec do
|
|
635
709
|
built_class
|
636
710
|
elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}") && !schema_name
|
637
711
|
# module_prefixes = type_name.split('::')
|
638
|
-
# path =
|
712
|
+
# path = base_module.name.split('::')[0..-2] + []
|
639
713
|
# module_prefixes.unshift('') unless module_prefixes.first.blank?
|
640
714
|
# candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
|
641
|
-
|
642
|
-
# elsif
|
715
|
+
base_module._brick_const_missing(*args)
|
716
|
+
# elsif base_module != Object
|
643
717
|
# module_parent.const_missing(*args)
|
644
718
|
else
|
645
|
-
puts "MISSING! #{
|
646
|
-
|
719
|
+
puts "MISSING! #{base_module.name} #{args.inspect} #{table_name}"
|
720
|
+
base_module._brick_const_missing(*args)
|
647
721
|
end
|
648
722
|
end
|
649
723
|
end
|
@@ -784,15 +858,15 @@ class Object
|
|
784
858
|
"#{hmt_fk}_through_#{hm.first[:assoc_name]}"
|
785
859
|
else # Use BT names to provide uniqueness
|
786
860
|
if self.name.underscore.singularize == hm.first[:alternate_name]
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
861
|
+
# Has previously been:
|
862
|
+
# # If it folds back on itself then look at the other side
|
863
|
+
# # (At this point just infer the source be the inverse of the first has_many that
|
864
|
+
# # we find that is not ourselves. If there are more than two then uh oh, can't
|
865
|
+
# # yet handle that rare circumstance!)
|
866
|
+
# other = hms.find { |hm1| hm1 != hm } # .first[:fk]
|
867
|
+
# options[:source] = other.first[:inverse][:assoc_name].to_sym
|
868
|
+
# And also has been:
|
869
|
+
# hm.first[:inverse][:assoc_name].to_sym
|
796
870
|
options[:source] = hm.last.to_sym
|
797
871
|
else
|
798
872
|
through = hm.first[:alternate_name].pluralize
|
@@ -926,21 +1000,26 @@ class Object
|
|
926
1000
|
(namespace || Object).const_set(class_name.to_sym, new_controller_class)
|
927
1001
|
|
928
1002
|
# Brick-specific pages
|
929
|
-
|
1003
|
+
case plural_class_name
|
1004
|
+
when 'BrickGem'
|
930
1005
|
self.define_method :orphans do
|
931
1006
|
instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params)))
|
932
1007
|
end
|
933
1008
|
return [new_controller_class, code + ' # BrickGem controller']
|
1009
|
+
when 'BrickSwagger'
|
1010
|
+
is_swagger = true # if request.format == :json)
|
934
1011
|
end
|
935
1012
|
|
936
|
-
unless (is_swagger = plural_class_name == 'BrickSwagger') # && request.format == :json)
|
937
|
-
code << " def index\n"
|
938
|
-
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
|
939
|
-
code << " @#{table_name}.brick_select(params)\n"
|
940
|
-
code << " end\n"
|
941
|
-
end
|
942
1013
|
self.protect_from_forgery unless: -> { self.request.format.js? }
|
943
1014
|
self.define_method :index do
|
1015
|
+
# We do all of this now so that bt_descrip and hm_counts are available on the model early in case the user
|
1016
|
+
# wants to do an ORDER BY based on any of that
|
1017
|
+
translations = {}
|
1018
|
+
join_array = ::Brick::JoinArray.new
|
1019
|
+
is_add_bts = is_add_hms = true
|
1020
|
+
# This builds out bt_descrip and hm_counts on the model
|
1021
|
+
model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
|
1022
|
+
|
944
1023
|
if is_swagger
|
945
1024
|
json = { 'openapi': '3.0.1', 'info': { 'title': 'API V1', 'version': 'v1' },
|
946
1025
|
'servers': [
|
@@ -990,6 +1069,32 @@ class Object
|
|
990
1069
|
render inline: json.to_json, content_type: request.format
|
991
1070
|
return
|
992
1071
|
end
|
1072
|
+
|
1073
|
+
# Normal (non-swagger) request
|
1074
|
+
|
1075
|
+
# %%% Allow params to define which columns to use for order_by
|
1076
|
+
ordering = if (order_tbl = ::Brick.config.order[table_name])
|
1077
|
+
case (order_default = order_tbl[:_brick_default])
|
1078
|
+
when Array
|
1079
|
+
order_default.map { |od_part| order_tbl[od_part] || od_part }
|
1080
|
+
when Symbol
|
1081
|
+
order_tbl[order_default] || order_default
|
1082
|
+
else
|
1083
|
+
pk
|
1084
|
+
end
|
1085
|
+
else
|
1086
|
+
pk # If it's not a custom ORDER BY, just use the key
|
1087
|
+
end
|
1088
|
+
order_by, order_by_txt = model._brick_calculate_ordering(ordering)
|
1089
|
+
if (order_params = params['_brick_order']&.split(',')&.map(&:to_sym)) # Overriding the default by providing a querystring param?
|
1090
|
+
order_by, _ = model._brick_calculate_ordering(order_params, true) # Don't do the txt part
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
code << " def index\n"
|
1094
|
+
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
1095
|
+
code << " @#{table_name}.brick_select(params)\n"
|
1096
|
+
code << " end\n"
|
1097
|
+
|
993
1098
|
::Brick.set_db_schema(params)
|
994
1099
|
if request.format == :csv # Asking for a template?
|
995
1100
|
require 'csv'
|
@@ -1003,19 +1108,16 @@ class Object
|
|
1003
1108
|
return
|
1004
1109
|
end
|
1005
1110
|
|
1006
|
-
|
1007
|
-
order = pk.each_with_object([]) { |pk_part, s| s << "#{quoted_table_name}.\"#{pk_part}\"" }
|
1008
|
-
ar_relation = order.present? ? model.order("#{order.join(', ')}") : model.all
|
1009
|
-
@_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
|
1111
|
+
@_brick_params = (ar_relation = model.all).brick_select(params, (selects = []), order_by, translations, join_array)
|
1010
1112
|
# %%% Add custom HM count columns
|
1011
1113
|
# %%% What happens when the PK is composite?
|
1012
|
-
counts =
|
1114
|
+
counts = model._br_hm_counts.each_with_object([]) { |v, s| s << "_br_#{v.first}._ct_ AS _br_#{v.first}_ct" }
|
1013
1115
|
instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
1014
1116
|
if namespace && (idx = lookup_context.prefixes.index(table_name))
|
1015
1117
|
lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
|
1016
1118
|
end
|
1017
|
-
@_brick_bt_descrip =
|
1018
|
-
@_brick_hm_counts =
|
1119
|
+
@_brick_bt_descrip = model._br_bt_descrip
|
1120
|
+
@_brick_hm_counts = model._br_hm_counts
|
1019
1121
|
@_brick_join_array = join_array
|
1020
1122
|
end
|
1021
1123
|
|
@@ -1049,7 +1151,17 @@ class Object
|
|
1049
1151
|
# end
|
1050
1152
|
|
1051
1153
|
is_need_params = true
|
1052
|
-
# code << " # (Define :
|
1154
|
+
# code << " # (Define :destroy)\n"
|
1155
|
+
code << " def edit\n"
|
1156
|
+
code << find_by_id
|
1157
|
+
code << " end\n"
|
1158
|
+
self.define_method :edit do
|
1159
|
+
::Brick.set_db_schema(params)
|
1160
|
+
id = is_pk_string ? params[:id] : params[:id]&.split(/[\/,_]/)
|
1161
|
+
id = id.first if id.is_a?(Array) && id.length == 1
|
1162
|
+
instance_variable_set("@#{singular_table_name}".to_sym, model.find(id))
|
1163
|
+
end
|
1164
|
+
|
1053
1165
|
code << " def update\n"
|
1054
1166
|
code << find_by_id
|
1055
1167
|
params_name = "#{singular_table_name}_params"
|
@@ -1083,8 +1195,12 @@ class Object
|
|
1083
1195
|
if is_need_params
|
1084
1196
|
code << "private\n"
|
1085
1197
|
code << " def #{params_name}\n"
|
1198
|
+
permits = model.columns_hash.keys.map { |c| c.to_sym.inspect } +
|
1199
|
+
model.reflect_on_all_associations.each_with_object([]) do |assoc, s|
|
1200
|
+
s << "#{assoc.name.to_s.singularize}_ids: []" if assoc.macro == :has_many && assoc.options[:through]
|
1201
|
+
end
|
1086
1202
|
code << " params.require(:#{require_name = model.name.underscore.tr('/', '_')
|
1087
|
-
}).permit(#{
|
1203
|
+
}).permit(#{permits.join(', ')})\n"
|
1088
1204
|
code << " end\n"
|
1089
1205
|
self.define_method(params_name) do
|
1090
1206
|
params.require(require_name.to_sym).permit(model.columns_hash.keys)
|
@@ -33,6 +33,9 @@ module Brick
|
|
33
33
|
# Additional references (virtual foreign keys)
|
34
34
|
::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)
|
35
35
|
|
36
|
+
# When table names have specific prefixes, automatically place them in their own module with a table_name_prefix.
|
37
|
+
::Brick.order = app.config.brick.fetch(:order, {})
|
38
|
+
|
36
39
|
# Skip creating a has_many association for these
|
37
40
|
::Brick.exclude_hms = app.config.brick.fetch(:exclude_hms, nil)
|
38
41
|
|
@@ -55,7 +58,9 @@ module Brick
|
|
55
58
|
def template_exists?(*args, **options)
|
56
59
|
(::Brick.config.add_orphans && args.first == 'orphans') ||
|
57
60
|
_brick_template_exists?(*args, **options) ||
|
58
|
-
|
61
|
+
# Do not auto-create a template when it's searching for an application.html.erb, which comes in like: ["edit", ["games", "application"]]
|
62
|
+
((args[1].length == 1 || args[1][-1] != 'application') &&
|
63
|
+
set_brick_model(args))
|
59
64
|
end
|
60
65
|
|
61
66
|
def set_brick_model(find_args)
|
@@ -101,7 +106,7 @@ module Brick
|
|
101
106
|
path_obj_name = model_name.underscore.tr('/', '_')
|
102
107
|
table_name = obj_name.pluralize
|
103
108
|
template_link = nil
|
104
|
-
bts, hms
|
109
|
+
bts, hms = ::Brick.get_bts_and_hms(@_brick_model) # This gets BT and HM and also has_many :through (HMT)
|
105
110
|
hms_columns = [] # Used for 'index'
|
106
111
|
skip_klass_hms = ::Brick.config.skip_index_hms[model_name] || {}
|
107
112
|
hms_headers = hms.each_with_object([]) do |hm, s|
|
@@ -109,7 +114,7 @@ module Brick
|
|
109
114
|
"H#{hm_assoc.macro == :has_one ? 'O' : 'M'}#{'T' if hm_assoc.options[:through]}",
|
110
115
|
(assoc_name = hm.first)]
|
111
116
|
hm_fk_name = if hm_assoc.options[:through]
|
112
|
-
associative =
|
117
|
+
associative = @_brick_model._br_associatives[hm.first]
|
113
118
|
tbl_nm = if hm_assoc.options[:source]
|
114
119
|
associative.klass.reflect_on_association(hm_assoc.options[:source]).inverse_of&.name
|
115
120
|
else
|
@@ -163,12 +168,12 @@ module Brick
|
|
163
168
|
# %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
|
164
169
|
# environment or whatever, then get either the controllers or routes list instead
|
165
170
|
apartment_default_schema = ::Brick.apartment_multitenant && Apartment.default_schema
|
166
|
-
table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).
|
171
|
+
table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).each_with_object({}) do |tbl, s|
|
167
172
|
if (tbl_parts = tbl.split('.')).first == apartment_default_schema
|
168
173
|
tbl = tbl_parts.last
|
169
174
|
end
|
170
|
-
tbl
|
171
|
-
end.sort.each_with_object(+'') do |v, s|
|
175
|
+
s[tbl] = nil
|
176
|
+
end.keys.sort.each_with_object(+'') do |v, s|
|
172
177
|
s << "<option value=\"#{v.underscore.gsub('.', '/').pluralize}\">#{v}</option>"
|
173
178
|
end.html_safe
|
174
179
|
table_options << '<option value="brick_orphans">(Orphans)</option>'.html_safe if is_orphans
|
@@ -199,6 +204,9 @@ table thead tr th, table tr th {
|
|
199
204
|
color: #fff;
|
200
205
|
text-align: left;
|
201
206
|
}
|
207
|
+
#headerTop th:hover, #headerTop th:hover {
|
208
|
+
background-color: #18B090;
|
209
|
+
}
|
202
210
|
table thead tr th a, table tr th a {
|
203
211
|
color: #80FFB8;
|
204
212
|
}
|
@@ -547,30 +555,33 @@ if (headerTop) {
|
|
547
555
|
<br>
|
548
556
|
<table id=\"headerTop\">
|
549
557
|
<table id=\"#{table_name}\">
|
550
|
-
<thead><tr>#{
|
558
|
+
<thead><tr>#{"<th x-order=\"#{pk.join(',')}\"></th>" if pk.present?}<%
|
551
559
|
col_order = []
|
552
560
|
@#{table_name}.columns.each do |col|
|
553
561
|
next if (#{(pk || []).inspect}.include?(col_name = col.name) && col.type == :integer && !bts.key?(col_name)) ||
|
554
562
|
::Brick.config.metadata_columns.include?(col_name) || poly_cols.include?(col_name)
|
555
563
|
|
556
564
|
col_order << col_name
|
557
|
-
%><th<%= \" title
|
565
|
+
%><th<%= \" title=\\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %><%
|
558
566
|
if (bt = bts[col_name]) %>
|
559
|
-
BT
|
567
|
+
<%= \" x-order=\\\"#\{bt.first}\\\"\".html_safe unless bt[2] # Allow sorting any BT except polymorphics
|
568
|
+
%>>BT <%
|
560
569
|
bt[1].each do |bt_pair| %><%=
|
561
570
|
bt_pair.first.bt_link(bt.first) %> <%
|
562
571
|
end %><%
|
563
|
-
else %><%=
|
564
|
-
col_name %><%
|
572
|
+
else %><%= \" x-order=\\\"#\{col_name}\\\"\".html_safe if true # Currently we always allow click to sort
|
573
|
+
%>><%= col_name %><%
|
565
574
|
end
|
566
575
|
%></th><%
|
567
576
|
end
|
568
577
|
# Consider getting the name from the association -- h.first.name -- if a more \"friendly\" alias should be used for a screwy table name
|
569
578
|
%>#{hms_headers.map do |h|
|
579
|
+
# Currently we always allow click to sort
|
580
|
+
"<th#{" x-order=\"#{h.first.name}\"" if true}>" +
|
570
581
|
if h.first.options[:through] && !h.first.through_reflection
|
571
|
-
"
|
582
|
+
"#{h[1]} #{h[2]} %></th>" # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
|
572
583
|
else
|
573
|
-
"
|
584
|
+
"#{h[1]} <%= link_to('#{h[2]}', #{h.first.klass.name.underscore.tr('/', '_').pluralize}_path) %></th>"
|
574
585
|
end
|
575
586
|
end.join
|
576
587
|
}</tr></thead>
|
@@ -658,7 +669,7 @@ end
|
|
658
669
|
<tr>
|
659
670
|
<% next if (#{(pk || []).inspect}.include?(k) && !bts.key?(k)) ||
|
660
671
|
::Brick.config.metadata_columns.include?(k) %>
|
661
|
-
<th class=\"show-field\"<%= \" title
|
672
|
+
<th class=\"show-field\"<%= \" title=\\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %>>
|
662
673
|
<% has_fields = true
|
663
674
|
if (bt = bts[k])
|
664
675
|
# Add a final member in this array with descriptive options to be used in <select> drop-downs
|
@@ -729,9 +740,14 @@ end
|
|
729
740
|
<% when :uuid %>
|
730
741
|
<%=
|
731
742
|
# Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
|
732
|
-
# If it's not yet enabled then:
|
743
|
+
# If it's not yet enabled then: create extension \"uuid-ossp\";
|
733
744
|
# ActiveUUID gem created a new :uuid type
|
734
745
|
val %>
|
746
|
+
<% when :ltree %>
|
747
|
+
<%=
|
748
|
+
# In Postgres labels of data stored in a hierarchical tree-like structure
|
749
|
+
# If it's not yet enabled then: create extension ltree;
|
750
|
+
val %>
|
735
751
|
<% when :binary, :primary_key %>
|
736
752
|
<% end %>
|
737
753
|
<% end %>
|
@@ -789,6 +805,15 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
|
|
789
805
|
</script>
|
790
806
|
<% end %>
|
791
807
|
<script>
|
808
|
+
<% # Make column headers sort when clicked
|
809
|
+
# %%% Create a smart javascript routine which can do this client-side %>
|
810
|
+
[... document.getElementsByTagName(\"TH\")].forEach(function (th) {
|
811
|
+
th.addEventListener(\"click\", function (e) {
|
812
|
+
var xOrder;
|
813
|
+
if (xOrder = this.getAttribute(\"x-order\"))
|
814
|
+
location.href = changeout(location.href, \"_brick_order\", xOrder);
|
815
|
+
});
|
816
|
+
});
|
792
817
|
document.querySelectorAll(\"input, select\").forEach(function (inp) {
|
793
818
|
var origVal = getInpVal(),
|
794
819
|
prevVal = origVal;
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -172,14 +172,14 @@ module Brick
|
|
172
172
|
# Mark has_manys that go to an associative ("join") table so that they are skipped in the UI,
|
173
173
|
# as well as any possible polymorphic associations
|
174
174
|
skip_hms = {}
|
175
|
-
|
175
|
+
hms.each do |hmt|
|
176
176
|
if (through = hmt.last.options[:through])
|
177
177
|
skip_hms[through] = nil # if hms[through]
|
178
178
|
# binding.pry if !hms[through]
|
179
179
|
# End up with a hash of HMT names pointing to join-table associations
|
180
180
|
# Last part was: hmt.last.name
|
181
181
|
# Changed up because looking for: hms[:issue_issue_duplicates]
|
182
|
-
|
182
|
+
model._br_associatives[hmt.first] = hms[through] # || hms["#{(opt = hmt.last.options)[:through].to_s.singularize}_#{opt[:source].to_s.pluralize}".to_sym]
|
183
183
|
elsif hmt.last.inverse_of.nil?
|
184
184
|
puts "SKIPPING #{hmt.last.name.inspect}"
|
185
185
|
# %%% If we don't do this then below associative.name will find that associative is nil
|
@@ -187,7 +187,7 @@ module Brick
|
|
187
187
|
end
|
188
188
|
end
|
189
189
|
skip_hms.each { |k, _v| hms.delete(k) }
|
190
|
-
[bts, hms
|
190
|
+
[bts, hms]
|
191
191
|
end
|
192
192
|
|
193
193
|
# Switches Brick auto-models on or off, for all threads
|
@@ -283,6 +283,11 @@ module Brick
|
|
283
283
|
end
|
284
284
|
end
|
285
285
|
|
286
|
+
# @api public
|
287
|
+
def order=(value)
|
288
|
+
Brick.config.order = value
|
289
|
+
end
|
290
|
+
|
286
291
|
# Skip creating a has_many association for these
|
287
292
|
# (Uses the same exact three-part format as would define an additional_reference)
|
288
293
|
# @api public
|
@@ -159,6 +159,41 @@ module Brick
|
|
159
159
|
# # When table names have specific prefixes automatically place them in their own module with a table_name_prefix.
|
160
160
|
# Brick.table_name_prefixes = { 'nav_' => 'Navigation' }
|
161
161
|
|
162
|
+
# # COLUMN SEQUENCING AND INCLUSION / EXCLUSION
|
163
|
+
|
164
|
+
# # By default if there is a primary key present then rows in an index view are ordered by this primary key. To
|
165
|
+
# # use a different rule for doing ORDER BY, you can override this default ordering done by The Brick, for instance
|
166
|
+
# # to have the rows in a contact list sorted by email:
|
167
|
+
# Brick.order = { 'contacts' => { _brick_default: :email } }
|
168
|
+
# # or by last name then first name:
|
169
|
+
# Brick.order = { 'contacts' => { _brick_default: [:lastname, :firstname] } }
|
170
|
+
# # Totally legitimate to have the default order be the name of a belongs_to or has_many association instead of an
|
171
|
+
# # actual column name, in which case for has_many it just orders by the count of how many records are associated,
|
172
|
+
# # and for belongs_to it's based on the primary table's DSL if any is defined (since that is what is used to
|
173
|
+
# # calculate what is shown when a foreign table lists out related records). If contacts relates to addresses,
|
174
|
+
# # then this is perfectly fine:
|
175
|
+
# Brick.order = { 'contacts' => { _brick_default: :address } }
|
176
|
+
# # You can even have a specific custom clause used in the ORDER BY. In this case it is recommended to include a
|
177
|
+
# # special placeholder for the table name with the sequence \"^^^\". Here is an example of having the default
|
178
|
+
# # ordering happening on the \"code\" column, and also defining custom sorting to be done, in this case proper
|
179
|
+
# # ordering if that code is stored as a dotted numeric value:
|
180
|
+
# Brick.order = { 'document_trees' => { _brick_default: :code,
|
181
|
+
# code: \"ORDER BY STRING_TO_ARRAY(^^^.code, '.')::int[]\" } }
|
182
|
+
|
183
|
+
# # Sequence of columns for each model. This also allows you to add read-only calculated columns in the same
|
184
|
+
# # kind of way that they can be added in the include: portion of include/exclude columns, below.
|
185
|
+
# # Designated by { <table name> => [<column name>, <column name>] }
|
186
|
+
# Brick.column_sequence = { 'users' => ['email', 'profile.firstname', 'profile.lastname'] }
|
187
|
+
|
188
|
+
# # Specific columns to include or exclude for each model. If there are only inclusions then only those
|
189
|
+
# # columns show. If there are any exclusions then all non-excluded columns are attempted to be shown,
|
190
|
+
# # which negates the usefulness of inclusions except to add calculated column detail built from DSL.
|
191
|
+
# # Designated by <table name>.<column name>
|
192
|
+
# Brick.column_sequence = { 'users' => { include: ['email', 'profile.firstname', 'profile.lastname'] },
|
193
|
+
# 'profile' => { exclude: ['birthdate'] } }
|
194
|
+
|
195
|
+
# # EXTRA FOREIGN KEYS AND OTHER HAS_MANY SETTINGS
|
196
|
+
|
162
197
|
# # Additional table references which are used to create has_many / belongs_to associations inside auto-created
|
163
198
|
# # models. (You can consider these to be \"virtual foreign keys\" if you wish)... You only have to add these
|
164
199
|
# # in cases where your database for some reason does not have foreign key constraints defined. Sometimes for
|
@@ -176,8 +211,9 @@ module Brick
|
|
176
211
|
# Brick.exclude_hms = [['users', 'favourite_colour_id', 'colours']]
|
177
212
|
|
178
213
|
# # Skip showing counts for these specific has_many associations when building auto-generated #index views.
|
179
|
-
# # When there are related tables with a significant number of records, this can lessen
|
180
|
-
# # considerably, sometimes fixing what might appear to be an index page that just \"hangs\"
|
214
|
+
# # When there are related tables with a significant number of records (generally 100,000 or more), this can lessen
|
215
|
+
# # the load on the database considerably, sometimes fixing what might appear to be an index page that just \"hangs\"
|
216
|
+
# # for no apparent reason.
|
181
217
|
# Brick.skip_index_hms = ['User.litany_of_woes']
|
182
218
|
|
183
219
|
# # By default primary tables involved in a foreign key relationship will indicate a \"has_many\" relationship pointing
|
@@ -199,6 +235,8 @@ module Brick
|
|
199
235
|
# # Designated by <table name>.<column name>
|
200
236
|
# Brick.not_nullables = ['users.name']
|
201
237
|
|
238
|
+
# # FRIENDLY DSL
|
239
|
+
|
202
240
|
# # A simple DSL is available to allow more user-friendly display of objects. Normally a user object might be shown
|
203
241
|
# # as its first non-metadata column, or if that is not available then something like \"User #45\" where 45 is that
|
204
242
|
# # object's ID. If there is no primary key then even that is not possible, so the object's .to_s method is called.
|
@@ -206,6 +244,8 @@ module Brick
|
|
206
244
|
# # user, then you can use model_descrips like this, putting expressions with property references in square brackets:
|
207
245
|
# Brick.model_descrips = { 'User' => '[profile.firstname] [profile.lastname]' }
|
208
246
|
|
247
|
+
# # SINGLE TABLE INHERITANCE
|
248
|
+
|
209
249
|
# # Specify STI subclasses either directly by name or as a general module prefix that should always relate to a specific
|
210
250
|
# # parent STI class. The prefixed :: here for these examples is mandatory. Also having a suffixed :: means instead of
|
211
251
|
# # a class reference, this is for a general namespace reference. So in this case requests for, say, either of the
|
@@ -221,6 +261,8 @@ module Brick
|
|
221
261
|
# Brick.sti_type_column = 'sti_type'
|
222
262
|
# Brick.sti_type_column = { 'rails_type' => ['sales.specialoffer'] }
|
223
263
|
|
264
|
+
# # POLYMORPHIC ASSOCIATIONS
|
265
|
+
|
224
266
|
# # Database schema to use when analysing existing data, such as deriving a list of polymorphic classes in the case that
|
225
267
|
# # it wasn't originally specified.
|
226
268
|
# Brick.schema_behavior = :namespaced
|
@@ -231,6 +273,8 @@ module Brick
|
|
231
273
|
|
232
274
|
# # Polymorphic associations are set up by providing a model name and polymorphic association name#{poly}
|
233
275
|
|
276
|
+
# # DEFAULT ROOT ROUTE
|
277
|
+
|
234
278
|
# # If a default route is not supplied, Brick attempts to find the most \"central\" table and wires up the default
|
235
279
|
# # route to go to the :index action for what would be a controller for that table. You can specify any controller
|
236
280
|
# # name and action you wish in order to override this and have that be the default route when none other has been
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.51
|
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-
|
11
|
+
date: 2022-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|