brick 1.0.46 → 1.0.49

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: 1442fb46672a819689a1f15aa16e2dd53647a057ea38948431746d3d8baf66f0
4
- data.tar.gz: 38d26e09e226d37e87737cba35f76ee3b2413dc4474ac8ecba582297205789aa
3
+ metadata.gz: 8b42f1ad45e6dc942ed2e875d39318f8705f9a467c0cb31f267a71a01053bfd4
4
+ data.tar.gz: 1e8f0cd1c374c5d75be4e4f1a8b659dd32b46f991c85cce4d234a6949d017e4b
5
5
  SHA512:
6
- metadata.gz: 8cb9e747cffb1e33f045f07656891b8e17ac4349661f897aa01886bae6fb7c2e4687f1021e04173266ab60cf330ec71f58514aeb670669f30ea8c6dd4140e943
7
- data.tar.gz: 54166ef84adaeab00c2d6408e9a9a6c5da5998bd278b0db490943d2d8da2701938cdc87a4c40ff65ac2cbe7196a7eb014a4b3b08dfd2ff5aa2ec426af00d38ab
6
+ metadata.gz: 437d415278f01a2ffced31687f77324c21416e6e7ef395d6a5d3db5a1853ccfb301c01716747ba4427417164a1200100205e7b7c75443bef83f1050f01683dda
7
+ data.tar.gz: d42367f291f2900876c5263aab30280f7b5b3d9f205c4199349b9e24d35326b5457e1db8fd0d39a512f59af9691bbe44e29dd05d3384d5a0013c05ffe6447d8c
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
@@ -221,6 +221,62 @@ module ActiveRecord
221
221
  template
222
222
  end
223
223
 
224
+ # belongs_to DSL descriptions
225
+ def self._br_bt_descrip
226
+ @_br_bt_descrip ||= {}
227
+ end
228
+ # has_many count definitions
229
+ def self._br_hm_counts
230
+ @_br_hm_counts ||= {}
231
+ end
232
+
233
+ # Search for BT, HM, and HMT DSL stuff
234
+ def self._brick_calculate_bts_hms(translations, join_array)
235
+ bts, hms, associatives = ::Brick.get_bts_and_hms(self)
236
+ bts.each do |_k, bt|
237
+ next if bt[2] # Polymorphic?
238
+
239
+ # join_array will receive this relation name when calling #brick_parse_dsl
240
+ _br_bt_descrip[bt.first] = if bt[1].is_a?(Array)
241
+ bt[1].each_with_object({}) { |bt_class, s| s[bt_class] = bt_class.brick_parse_dsl(join_array, bt.first, translations, true) }
242
+ else
243
+ { bt.last => bt[1].brick_parse_dsl(join_array, bt.first, translations) }
244
+ end
245
+ end
246
+ skip_klass_hms = ::Brick.config.skip_index_hms[self.name] || {}
247
+ hms.each do |k, hm|
248
+ next if skip_klass_hms.key?(k)
249
+
250
+ if hm.macro == :has_one
251
+ # For our purposes a :has_one is similar enough to a :belongs_to that we can just join forces
252
+ _br_bt_descrip[k] = { hm.klass => hm.klass.brick_parse_dsl(join_array, k, translations) }
253
+ else # Standard :has_many
254
+ _br_hm_counts[k] = hm
255
+ end
256
+ end
257
+ end
258
+
259
+ def self._brick_calculate_ordering(ordering, is_do_txt = true)
260
+ quoted_table_name = table_name.split('.').map { |x| "\"#{x}\"" }.join('.')
261
+ order_by_txt = [] if is_do_txt
262
+ ordering = [ordering] if ordering && !ordering.is_a?(Array)
263
+ 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
264
+ case ord_part
265
+ when String
266
+ ord_expr = ord_part.gsub('^^^', quoted_table_name)
267
+ order_by_txt&.<<("Arel.sql(#{ord_expr})")
268
+ Arel.sql(ord_expr)
269
+ else # Expecting only Symbol
270
+ ord_part = "_br_#{ord_part}_ct" if _br_hm_counts.key?(ord_part)
271
+ # Retain any reference to a bt_descrip as being a symbol
272
+ # Was: "#{quoted_table_name}.\"#{ord_part}\""
273
+ order_by_txt&.<<(_br_bt_descrip.key?(ord_part) ? ord_part : ord_part.inspect)
274
+ ord_part
275
+ end
276
+ end
277
+ [order_by, order_by_txt]
278
+ end
279
+
224
280
  private
225
281
 
226
282
  def self._brick_get_fks
@@ -310,13 +366,12 @@ module ActiveRecord
310
366
  end
311
367
  end
312
368
 
313
- def brick_select(params, selects = nil, bt_descrip = {}, hm_counts = {}, join_array = ::Brick::JoinArray.new
314
- # , is_add_bts, is_add_hms
315
- )
316
- is_add_bts = is_add_hms = true
369
+ def brick_select(params, selects = nil, order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
317
370
  is_distinct = nil
318
371
  wheres = {}
319
372
  params.each do |k, v|
373
+ next if ['_brick_schema', '_brick_order'].include?(k)
374
+
320
375
  case (ks = k.split('.')).length
321
376
  when 1
322
377
  next unless klass._brick_get_fks.include?(k)
@@ -345,33 +400,6 @@ module ActiveRecord
345
400
  end
346
401
  end
347
402
 
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
403
  if join_array.present?
376
404
  left_outer_joins!(join_array)
377
405
  # Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
@@ -380,7 +408,7 @@ module ActiveRecord
380
408
  chains = rel_dupe._brick_chains
381
409
  id_for_tables = Hash.new { |h, k| h[k] = [] }
382
410
  field_tbl_names = Hash.new { |h, k| h[k] = {} }
383
- bt_columns = bt_descrip.each_with_object([]) do |v, s|
411
+ bt_columns = klass._br_bt_descrip.each_with_object([]) do |v, s|
384
412
  v.last.each do |k1, v1| # k1 is class, v1 is array of columns to snag
385
413
  next if chains[k1].nil?
386
414
 
@@ -416,7 +444,7 @@ module ActiveRecord
416
444
  end
417
445
  end
418
446
  # Add derived table JOIN for the has_many counts
419
- hm_counts.each do |k, hm|
447
+ klass._br_hm_counts.each do |k, hm|
420
448
  associative = nil
421
449
  count_column = if hm.options[:through]
422
450
  fk_col = (associative = associatives[hm.name])&.foreign_key
@@ -450,12 +478,31 @@ JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}
450
478
  joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
451
479
  end
452
480
  where!(wheres) unless wheres.empty?
481
+ # Must parse the order_by and see if there are any symbols which refer to BT associations
482
+ # as they must be expanded to find the corresponding _br_model__column naming for each.
483
+ if order_by.present?
484
+ final_order_by = *order_by.each_with_object([]) do |v, s|
485
+ if v.is_a?(Symbol)
486
+ # Add the ordered series of columns derived from the BT based on its DSL
487
+ if (bt_cols = klass._br_bt_descrip[v])
488
+ bt_cols.values.each do |v1|
489
+ v1.each { |v2| s << v2.last if v2.length > 1 }
490
+ end
491
+ else
492
+ s << v
493
+ end
494
+ else # String stuff just comes straight through
495
+ s << v
496
+ end
497
+ end
498
+ order!(*final_order_by)
499
+ end
453
500
  limit!(1000) # Don't want to get too carried away just yet
454
501
  wheres unless wheres.empty? # Return the specific parameters that we did use
455
502
  end
456
503
 
457
504
  private
458
-
505
+
459
506
  def shift_or_first(ary)
460
507
  ary.length > 1 ? ary.shift : ary.first
461
508
  end
@@ -596,7 +643,7 @@ Module.class_exec do
596
643
  # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
597
644
  # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
598
645
 
599
- if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
646
+ if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{self.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
600
647
  self != Object # ... or otherwise already in some namespace?
601
648
  schema_name = [(singular_schema_name = name.underscore),
602
649
  (schema_name = singular_schema_name.pluralize),
@@ -619,7 +666,7 @@ Module.class_exec do
619
666
  else
620
667
  ActiveSupport::Inflector.pluralize(singular_table_name)
621
668
  end
622
- if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') &&
669
+ if ::Brick.apartment_multitenant &&
623
670
  Apartment.excluded_models.include?(table_name.singularize.camelize)
624
671
  schema_name = Apartment.default_schema
625
672
  end
@@ -654,7 +701,7 @@ class Object
654
701
  private
655
702
 
656
703
  def build_model(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
657
- if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') &&
704
+ if ::Brick.apartment_multitenant &&
658
705
  schema_name == Apartment.default_schema
659
706
  relation = relations["#{schema_name}.#{matching}"]
660
707
  end
@@ -665,7 +712,7 @@ class Object
665
712
  schema_name
666
713
  else
667
714
  matching = "#{schema_name}.#{matching}"
668
- (Brick.db_schemas[schema_name] ||= self.const_get(schema_name.singularize.camelize))
715
+ (Brick.db_schemas[schema_name] ||= self.const_get(schema_name.camelize))
669
716
  end
670
717
  "#{schema_module&.name}::#{inheritable_name || model_name}"
671
718
  end
@@ -773,33 +820,47 @@ class Object
773
820
  end # class definition
774
821
  # Having this separate -- will this now work out better?
775
822
  built_model.class_exec do
776
- hmts&.each do |hmt_fk, fks|
823
+ hmts&.each do |hmt_fk, hms|
777
824
  hmt_fk = hmt_fk.tr('.', '_')
778
- fks.each do |fk|
779
- # %%% Will not work with custom has_many name
780
- through = ::Brick.config.schema_behavior[:multitenant] ? fk.first[:assoc_name] : fk.first[:inverse_table].tr('.', '_').pluralize
781
- hmt_name = if fks.length > 1
782
- if fks[0].first[:inverse][:assoc_name] == fks[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
783
- "#{hmt_fk}_through_#{fk.first[:assoc_name]}"
825
+ hms.each do |hm|
826
+ # %%% Need to confirm that HMTs work when they are built from has_manys with custom names
827
+ through = ::Brick.config.schema_behavior[:multitenant] ? hm.first[:assoc_name] : hm.first[:inverse_table].tr('.', '_').pluralize
828
+ options = {}
829
+ hmt_name = if hms.length > 1
830
+ if hms[0].first[:inverse][:assoc_name] == hms[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
831
+ "#{hmt_fk}_through_#{hm.first[:assoc_name]}"
784
832
  else # Use BT names to provide uniqueness
785
- through = fk.first[:alternate_name].pluralize
786
- singular_assoc_name = fk.first[:inverse][:assoc_name].singularize
833
+ if self.name.underscore.singularize == hm.first[:alternate_name]
834
+ # Has previously been:
835
+ # # If it folds back on itself then look at the other side
836
+ # # (At this point just infer the source be the inverse of the first has_many that
837
+ # # we find that is not ourselves. If there are more than two then uh oh, can't
838
+ # # yet handle that rare circumstance!)
839
+ # other = hms.find { |hm1| hm1 != hm } # .first[:fk]
840
+ # options[:source] = other.first[:inverse][:assoc_name].to_sym
841
+ # And also has been:
842
+ # hm.first[:inverse][:assoc_name].to_sym
843
+ options[:source] = hm.last.to_sym
844
+ else
845
+ through = hm.first[:alternate_name].pluralize
846
+ end
847
+ singular_assoc_name = hm.first[:inverse][:assoc_name].singularize
787
848
  "#{singular_assoc_name}_#{hmt_fk}"
788
849
  end
789
850
  else
790
851
  hmt_fk
791
852
  end
792
- options = { through: through.to_sym }
853
+ options[:through] = through.to_sym
793
854
  if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
794
- hmt_name = "#{hmt_name.singularize}_#{fk.first[:assoc_name]}"
855
+ hmt_name = "#{hmt_name.singularize}_#{hm.first[:assoc_name]}"
795
856
  # Was:
796
- # options[:class_name] = fk.first[:inverse_table].singularize.camelize
797
- # options[:foreign_key] = fk.first[:fk].to_sym
798
- far_assoc = relations[fk.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == fk.last }
857
+ # options[:class_name] = hm.first[:inverse_table].singularize.camelize
858
+ # options[:foreign_key] = hm.first[:fk].to_sym
859
+ far_assoc = relations[hm.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == hm.last }
799
860
  options[:class_name] = far_assoc.last[:inverse_table].singularize.camelize
800
861
  options[:foreign_key] = far_assoc.last[:fk].to_sym
801
862
  end
802
- options[:source] = fk.last.to_sym unless hmt_name.singularize == fk.last
863
+ options[:source] ||= hm.last.to_sym unless hmt_name.singularize == hm.last
803
864
  code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
804
865
  self.send(:has_many, hmt_name.to_sym, **options)
805
866
  end
@@ -832,7 +893,7 @@ class Object
832
893
  if (inverse = assoc[:inverse])
833
894
  # If it's multitenant with something like: public.____ ...
834
895
  if (it_parts = inverse_table.split('.')).length > 1 &&
835
- ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') &&
896
+ ::Brick.apartment_multitenant &&
836
897
  it_parts.first == Apartment.default_schema
837
898
  it_parts.shift # ... then ditch the generic schema name
838
899
  end
@@ -912,21 +973,26 @@ class Object
912
973
  (namespace || Object).const_set(class_name.to_sym, new_controller_class)
913
974
 
914
975
  # Brick-specific pages
915
- if plural_class_name == 'BrickGem'
976
+ case plural_class_name
977
+ when 'BrickGem'
916
978
  self.define_method :orphans do
917
979
  instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params)))
918
980
  end
919
981
  return [new_controller_class, code + ' # BrickGem controller']
982
+ when 'BrickSwagger'
983
+ is_swagger = true # if request.format == :json)
920
984
  end
921
985
 
922
- unless (is_swagger = plural_class_name == 'BrickSwagger') # && request.format == :json)
923
- code << " def index\n"
924
- code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
925
- code << " @#{table_name}.brick_select(params)\n"
926
- code << " end\n"
927
- end
928
986
  self.protect_from_forgery unless: -> { self.request.format.js? }
929
987
  self.define_method :index do
988
+ # We do all of this now so that bt_descrip and hm_counts are available on the model early in case the user
989
+ # wants to do an ORDER BY based on any of that
990
+ translations = {}
991
+ join_array = ::Brick::JoinArray.new
992
+ is_add_bts = is_add_hms = true
993
+ # This builds out bt_descrip and hm_counts on the model
994
+ model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
995
+
930
996
  if is_swagger
931
997
  json = { 'openapi': '3.0.1', 'info': { 'title': 'API V1', 'version': 'v1' },
932
998
  'servers': [
@@ -976,6 +1042,32 @@ class Object
976
1042
  render inline: json.to_json, content_type: request.format
977
1043
  return
978
1044
  end
1045
+
1046
+ # Normal (non-swagger) request
1047
+
1048
+ # %%% Allow params to define which columns to use for order_by
1049
+ ordering = if (order_tbl = ::Brick.config.order[table_name])
1050
+ case (order_default = order_tbl[:_brick_default])
1051
+ when Array
1052
+ order_default.map { |od_part| order_tbl[od_part] || od_part }
1053
+ when Symbol
1054
+ order_tbl[order_default] || order_default
1055
+ else
1056
+ pk
1057
+ end
1058
+ else
1059
+ pk # If it's not a custom ORDER BY, just use the key
1060
+ end
1061
+ order_by, order_by_txt = model._brick_calculate_ordering(ordering)
1062
+ if (order_params = params['_brick_order']&.split(',')&.map(&:to_sym)) # Overriding the default by providing a querystring param?
1063
+ order_by, _ = model._brick_calculate_ordering(order_params, true) # Don't do the txt part
1064
+ end
1065
+
1066
+ code << " def index\n"
1067
+ code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
1068
+ code << " @#{table_name}.brick_select(params)\n"
1069
+ code << " end\n"
1070
+
979
1071
  ::Brick.set_db_schema(params)
980
1072
  if request.format == :csv # Asking for a template?
981
1073
  require 'csv'
@@ -989,19 +1081,16 @@ class Object
989
1081
  return
990
1082
  end
991
1083
 
992
- quoted_table_name = model.table_name.split('.').map { |x| "\"#{x}\"" }.join('.')
993
- order = pk.each_with_object([]) { |pk_part, s| s << "#{quoted_table_name}.\"#{pk_part}\"" }
994
- ar_relation = order.present? ? model.order("#{order.join(', ')}") : model.all
995
- @_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
1084
+ @_brick_params = (ar_relation = model.all).brick_select(params, (selects = []), order_by, translations, join_array)
996
1085
  # %%% Add custom HM count columns
997
1086
  # %%% What happens when the PK is composite?
998
- counts = hm_counts.each_with_object([]) { |v, s| s << "_br_#{v.first}._ct_ AS _br_#{v.first}_ct" }
1087
+ counts = model._br_hm_counts.each_with_object([]) { |v, s| s << "_br_#{v.first}._ct_ AS _br_#{v.first}_ct" }
999
1088
  instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
1000
1089
  if namespace && (idx = lookup_context.prefixes.index(table_name))
1001
1090
  lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
1002
1091
  end
1003
- @_brick_bt_descrip = bt_descrip
1004
- @_brick_hm_counts = hm_counts
1092
+ @_brick_bt_descrip = model._br_bt_descrip
1093
+ @_brick_hm_counts = model._br_hm_counts
1005
1094
  @_brick_join_array = join_array
1006
1095
  end
1007
1096
 
@@ -1128,7 +1217,8 @@ module ActiveRecord::ConnectionHandling
1128
1217
  end
1129
1218
  # Load the initializer for the Apartment gem a little early so that if .excluded_models and
1130
1219
  # .default_schema are specified then we can work with non-tenanted models more appropriately
1131
- if Object.const_defined?('Apartment') && File.exist?(apartment_initializer = Rails.root.join('config/initializers/apartment.rb'))
1220
+ apartment = Object.const_defined?('Apartment')
1221
+ if apartment && File.exist?(apartment_initializer = Rails.root.join('config/initializers/apartment.rb'))
1132
1222
  load apartment_initializer
1133
1223
  apartment_excluded = Apartment.excluded_models
1134
1224
  end
@@ -1331,17 +1421,20 @@ module ActiveRecord::ConnectionHandling
1331
1421
  end
1332
1422
  end
1333
1423
 
1334
- apartment = Object.const_defined?('Apartment') && Apartment
1335
1424
  tables = []
1336
1425
  views = []
1337
1426
  relations.each do |k, v|
1338
1427
  name_parts = k.split('.')
1428
+ idx = 1
1429
+ name_parts = name_parts.map do |x|
1430
+ ((idx += 1) < name_parts.length ? x.singularize : x).camelize
1431
+ end
1339
1432
  if v.key?(:isView)
1340
1433
  views
1341
1434
  else
1342
1435
  name_parts.shift if apartment && name_parts.length > 1 && name_parts.first == Apartment.default_schema
1343
1436
  tables
1344
- end << name_parts.map { |x| x.singularize.camelize }.join('::')
1437
+ end << name_parts.join('::')
1345
1438
  end
1346
1439
  unless tables.empty?
1347
1440
  puts "\nClasses that can be built from tables:"
@@ -1423,7 +1516,7 @@ module Brick
1423
1516
  unless (cnstr_name = fk[5])
1424
1517
  # For any appended references (those that come from config), arrive upon a definitely unique constraint name
1425
1518
  pri_tbl = is_class ? fk[4][:class].underscore : pri_tbl
1426
- pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl.singularize != bt_assoc_name
1519
+ pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl&.singularize != bt_assoc_name
1427
1520
  cnstr_base = cnstr_name = "(brick) #{for_tbl}_#{pri_tbl}"
1428
1521
  cnstr_added_num = 1
1429
1522
  cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
@@ -1449,6 +1542,8 @@ module Brick
1449
1542
  return
1450
1543
  end
1451
1544
  end
1545
+ return unless bts # Rails 5.0 and older can have bts end up being nil
1546
+
1452
1547
  if (assoc_bt = bts[cnstr_name])
1453
1548
  if is_polymorphic
1454
1549
  # Assuming same fk (don't yet support composite keys for polymorphics)
@@ -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
 
@@ -105,13 +108,26 @@ module Brick
105
108
  hms_columns = [] # Used for 'index'
106
109
  skip_klass_hms = ::Brick.config.skip_index_hms[model_name] || {}
107
110
  hms_headers = hms.each_with_object([]) do |hm, s|
108
- hm_stuff = [(hm_assoc = hm.last), "H#{hm_assoc.macro == :has_one ? 'O' : 'M'}#{'T' if hm_assoc.options[:through]}", (assoc_name = hm.first)]
111
+ hm_stuff = [(hm_assoc = hm.last),
112
+ "H#{hm_assoc.macro == :has_one ? 'O' : 'M'}#{'T' if hm_assoc.options[:through]}",
113
+ (assoc_name = hm.first)]
109
114
  hm_fk_name = if hm_assoc.options[:through]
110
- associative = associatives[hm_assoc.name]
111
- associative && "'#{associative.name}.#{associative.foreign_key}'"
112
- else
113
- hm_assoc.foreign_key
114
- end
115
+ associative = associatives[hm.first]
116
+ tbl_nm = if hm_assoc.options[:source]
117
+ associative.klass.reflect_on_association(hm_assoc.options[:source]).inverse_of&.name
118
+ else
119
+ associative.name
120
+ end
121
+ # If there is no inverse available for the source belongs_to association, make one based on the class name
122
+ unless tbl_nm
123
+ tbl_nm = associative.class_name.underscore
124
+ tbl_nm.slice!(0) if tbl_nm[0] == ('/')
125
+ tbl_nm = tbl_nm.tr('/', '_').pluralize
126
+ end
127
+ "'#{tbl_nm}.#{associative.foreign_key}'"
128
+ else
129
+ hm_assoc.foreign_key
130
+ end
115
131
  case args.first
116
132
  when 'index'
117
133
  hms_columns << if hm_assoc.macro == :has_many
@@ -149,7 +165,7 @@ module Brick
149
165
  schema_options = ::Brick.db_schemas.keys.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
150
166
  # %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
151
167
  # environment or whatever, then get either the controllers or routes list instead
152
- apartment_default_schema = ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && Apartment.default_schema
168
+ apartment_default_schema = ::Brick.apartment_multitenant && Apartment.default_schema
153
169
  table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).map do |tbl|
154
170
  if (tbl_parts = tbl.split('.')).first == apartment_default_schema
155
171
  tbl = tbl_parts.last
@@ -186,6 +202,9 @@ table thead tr th, table tr th {
186
202
  color: #fff;
187
203
  text-align: left;
188
204
  }
205
+ #headerTop th:hover, #headerTop th:hover {
206
+ background-color: #18B090;
207
+ }
189
208
  table thead tr th a, table tr th a {
190
209
  color: #80FFB8;
191
210
  }
@@ -524,7 +543,7 @@ if (headerTop) {
524
543
  k, id = @_brick_params.first
525
544
  id = id.first if id.is_a?(Array) && id.length == 1
526
545
  origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
527
- if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| puts fk.inspect; fk[:fk] == key_parts.last }) &&
546
+ if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| fk[:fk] == key_parts.last }) &&
528
547
  (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
529
548
  <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.name.underscore.tr('/', '_')\}_path\".to_sym, id) %></h3><%
530
549
  end
@@ -534,30 +553,33 @@ if (headerTop) {
534
553
  <br>
535
554
  <table id=\"headerTop\">
536
555
  <table id=\"#{table_name}\">
537
- <thead><tr>#{'<th></th>' if pk.present?}<%
556
+ <thead><tr>#{"<th x-order=\"#{pk.join(',')}\"></th>" if pk.present?}<%
538
557
  col_order = []
539
558
  @#{table_name}.columns.each do |col|
540
559
  next if (#{(pk || []).inspect}.include?(col_name = col.name) && col.type == :integer && !bts.key?(col_name)) ||
541
560
  ::Brick.config.metadata_columns.include?(col_name) || poly_cols.include?(col_name)
542
561
 
543
562
  col_order << col_name
544
- %><th<%= \" title = \\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %>><%
563
+ %><th<%= \" title=\\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %><%
545
564
  if (bt = bts[col_name]) %>
546
- BT <%
565
+ <%= \" x-order=\\\"#\{bt.first}\\\"\".html_safe if true # Currently we always allow click to sort
566
+ %>>BT <%
547
567
  bt[1].each do |bt_pair| %><%=
548
568
  bt_pair.first.bt_link(bt.first) %> <%
549
569
  end %><%
550
- else %><%=
551
- col_name %><%
570
+ else %><%= \" x-order=\\\"#\{col_name}\\\"\".html_safe if true # Currently we always allow click to sort
571
+ %>><%= col_name %><%
552
572
  end
553
573
  %></th><%
554
574
  end
555
575
  # Consider getting the name from the association -- h.first.name -- if a more \"friendly\" alias should be used for a screwy table name
556
576
  %>#{hms_headers.map do |h|
577
+ # Currently we always allow click to sort
578
+ "<th#{" x-order=\"#{h.first.name}\"" if true}>" +
557
579
  if h.first.options[:through] && !h.first.through_reflection
558
- "<th>#{h[1]} #{h[2]} %></th>" # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
580
+ "#{h[1]} #{h[2]} %></th>" # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
559
581
  else
560
- "<th>#{h[1]} <%= link_to('#{h[2]}', #{h.first.klass.name.underscore.tr('/', '_').pluralize}_path) %></th>"
582
+ "#{h[1]} <%= link_to('#{h[2]}', #{h.first.klass.name.underscore.tr('/', '_').pluralize}_path) %></th>"
561
583
  end
562
584
  end.join
563
585
  }</tr></thead>
@@ -645,7 +667,7 @@ end
645
667
  <tr>
646
668
  <% next if (#{(pk || []).inspect}.include?(k) && !bts.key?(k)) ||
647
669
  ::Brick.config.metadata_columns.include?(k) %>
648
- <th class=\"show-field\"<%= \" title = \\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %>>
670
+ <th class=\"show-field\"<%= \" title=\\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %>>
649
671
  <% has_fields = true
650
672
  if (bt = bts[k])
651
673
  # Add a final member in this array with descriptive options to be used in <select> drop-downs
@@ -716,9 +738,14 @@ end
716
738
  <% when :uuid %>
717
739
  <%=
718
740
  # Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
719
- # If it's not yet enabled then: enable_extension 'uuid-ossp'
741
+ # If it's not yet enabled then: create extension \"uuid-ossp\";
720
742
  # ActiveUUID gem created a new :uuid type
721
743
  val %>
744
+ <% when :ltree %>
745
+ <%=
746
+ # In Postgres labels of data stored in a hierarchical tree-like structure
747
+ # If it's not yet enabled then: create extension ltree;
748
+ val %>
722
749
  <% when :binary, :primary_key %>
723
750
  <% end %>
724
751
  <% end %>
@@ -776,6 +803,13 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
776
803
  </script>
777
804
  <% end %>
778
805
  <script>
806
+ <% # Make column headers sort when clicked
807
+ # %%% Create a smart javascript routine which can do this client-side %>
808
+ [... document.getElementsByTagName(\"TH\")].forEach(function (th) {
809
+ th.addEventListener(\"click\", function (e) {
810
+ location.href = changeout(location.href, \"_brick_order\", this.getAttribute(\"x-order\"));
811
+ });
812
+ });
779
813
  document.querySelectorAll(\"input, select\").forEach(function (inp) {
780
814
  var origVal = getInpVal(),
781
815
  prevVal = origVal;
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 46
8
+ TINY = 49
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
@@ -47,7 +47,6 @@ end
47
47
 
48
48
  # Add left_outer_join! to Associations::JoinDependency and Relation::QueryMethods
49
49
  if ActiveRecord.version < ::Gem::Version.new('5')
50
- is_add_left_outer_join = true
51
50
  ::Brick::Util._patch_require(
52
51
  'active_record/associations/join_dependency.rb', '/activerecord', # /associations
53
52
  ["def join_constraints(outer_joins)
@@ -140,6 +139,13 @@ module Brick
140
139
  (@relations ||= {})[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } }
141
140
  end
142
141
 
142
+ def apartment_multitenant
143
+ if @apartment_multitenant.nil?
144
+ @apartment_multitenant = ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment')
145
+ end
146
+ @apartment_multitenant
147
+ end
148
+
143
149
  # If multitenancy is enabled, a list of non-tenanted "global" models
144
150
  def non_tenanted_models
145
151
  @pending_models ||= {}
@@ -168,8 +174,12 @@ module Brick
168
174
  skip_hms = {}
169
175
  associatives = hms.each_with_object({}) do |hmt, s|
170
176
  if (through = hmt.last.options[:through])
171
- skip_hms[through] = nil
172
- s[hmt.first] = hms[through] # End up with a hash of HMT names pointing to join-table associations
177
+ skip_hms[through] = nil # if hms[through]
178
+ # binding.pry if !hms[through]
179
+ # End up with a hash of HMT names pointing to join-table associations
180
+ # Last part was: hmt.last.name
181
+ # Changed up because looking for: hms[:issue_issue_duplicates]
182
+ s[hmt.first] = hms[through] # || hms["#{(opt = hmt.last.options)[:through].to_s.singularize}_#{opt[:source].to_s.pluralize}".to_sym]
173
183
  elsif hmt.last.inverse_of.nil?
174
184
  puts "SKIPPING #{hmt.last.name.inspect}"
175
185
  # %%% If we don't do this then below associative.name will find that associative is nil
@@ -273,6 +283,11 @@ module Brick
273
283
  end
274
284
  end
275
285
 
286
+ # @api public
287
+ def order=(value)
288
+ Brick.config.order = value
289
+ end
290
+
276
291
  # Skip creating a has_many association for these
277
292
  # (Uses the same exact three-part format as would define an additional_reference)
278
293
  # @api public
@@ -447,7 +462,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
447
462
  ::Brick.relations.each do |rel_name, v|
448
463
  rel_name = rel_name.split('.').map(&:underscore)
449
464
  schema_names = rel_name[0..-2]
450
- schema_names.shift if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_names.first == Apartment.default_schema
465
+ schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == Apartment.default_schema
451
466
  k = rel_name.last
452
467
  unless existing_controllers.key?(controller_name = k.pluralize)
453
468
  options = {}
@@ -477,7 +492,8 @@ require 'brick/version_number'
477
492
  # Older versions of ActiveRecord would only show more serious error information from "panic" level, which is
478
493
  # a level only available in Postgres 12 and older. This patch will allow older and newer versions of Postgres
479
494
  # to work along with fairly old versions of Rails.
480
- if Object.const_defined?('PG::VERSION') && ActiveRecord.version < ::Gem::Version.new('4.2.6')
495
+ if (is_postgres = (Object.const_defined?('PG::VERSION') || Gem::Specification.find_all_by_name('pg').present?)) &&
496
+ ActiveRecord.version < ::Gem::Version.new('4.2.6')
481
497
  ::Brick::Util._patch_require(
482
498
  'active_record/connection_adapters/postgresql_adapter.rb', '/activerecord', ["'panic'", "'error'"]
483
499
  )
@@ -485,13 +501,28 @@ end
485
501
 
486
502
  require 'active_record'
487
503
  require 'active_record/relation'
488
- require 'active_record/relation/query_methods' if is_add_left_outer_join
504
+ # To support adding left_outer_join
505
+ require 'active_record/relation/query_methods' if ActiveRecord.version < ::Gem::Version.new('5')
506
+ require 'rails/railtie' if ActiveRecord.version < ::Gem::Version.new('4.2')
489
507
 
490
508
  # Rake tasks
491
509
  class Railtie < Rails::Railtie
492
510
  Dir.glob("#{File.expand_path(__dir__)}/brick/tasks/**/*.rake").each { |task| load task }
493
511
  end
494
512
 
513
+ # Rails < 4.2 does not have env
514
+ module Rails
515
+ unless respond_to?(:env)
516
+ def self.env
517
+ @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development")
518
+ end
519
+
520
+ def self.env=(environment)
521
+ @_env = ActiveSupport::StringInquirer.new(environment)
522
+ end
523
+ end
524
+ end
525
+
495
526
  # Major compatibility fixes for ActiveRecord < 4.2
496
527
  # ================================================
497
528
  ActiveSupport.on_load(:active_record) do
@@ -506,6 +537,15 @@ ActiveSupport.on_load(:active_record) do
506
537
  end
507
538
  end
508
539
  end
540
+ # ActiveRecord < 4.2 does not have default_timezone
541
+ # :singleton-method:
542
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
543
+ # dates and times from the database. This is set to :utc by default.
544
+ unless respond_to?(:default_timezone)
545
+ puts "ADDING!!! 4.w"
546
+ mattr_accessor :default_timezone, instance_writer: false
547
+ self.default_timezone = :utc
548
+ end
509
549
  end
510
550
 
511
551
  # Rails < 4.0 cannot do #find_by, #find_or_create_by, or do #pluck on multiple columns, so here are the patches:
@@ -711,58 +751,60 @@ ActiveSupport.on_load(:active_record) do
711
751
  end
712
752
  end
713
753
 
714
- if is_add_left_outer_join
715
- # Final pieces for left_outer_joins support, which was derived from this commit:
716
- # https://github.com/rails/rails/commit/3f46ef1ddab87482b730a3f53987e04308783d8b
717
- module Associations
718
- class JoinDependency
719
- def make_left_outer_joins(parent, child)
720
- tables = child.tables
721
- join_type = Arel::Nodes::OuterJoin
722
- info = make_constraints parent, child, tables, join_type
723
-
724
- [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
725
- end
754
+ # Final pieces for left_outer_joins support, which was derived from this commit:
755
+ # https://github.com/rails/rails/commit/3f46ef1ddab87482b730a3f53987e04308783d8b
756
+ module Associations
757
+ class JoinDependency
758
+ def make_left_outer_joins(parent, child)
759
+ tables = child.tables
760
+ join_type = Arel::Nodes::OuterJoin
761
+ info = make_constraints parent, child, tables, join_type
762
+
763
+ [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
726
764
  end
727
765
  end
728
- module Querying
729
- delegate :left_outer_joins, to: :all
766
+ end
767
+ module Querying
768
+ delegate :left_outer_joins, to: :all
769
+ end
770
+ class Relation
771
+ unless MULTI_VALUE_METHODS.include?(:left_outer_joins)
772
+ _multi_value_methods = MULTI_VALUE_METHODS + [:left_outer_joins]
773
+ send(:remove_const, :MULTI_VALUE_METHODS)
774
+ MULTI_VALUE_METHODS = _multi_value_methods
730
775
  end
731
- class Relation
732
- MULTI_VALUE_METHODS = MULTI_VALUE_METHODS + [:left_outer_joins] unless MULTI_VALUE_METHODS.include?(:left_outer_joins)
776
+ end
777
+ module QueryMethods
778
+ attr_writer :left_outer_joins_values
779
+ def left_outer_joins_values
780
+ @left_outer_joins_values ||= []
733
781
  end
734
- module QueryMethods
735
- attr_writer :left_outer_joins_values
736
- def left_outer_joins_values
737
- @left_outer_joins_values ||= []
738
- end
739
782
 
740
- def left_outer_joins(*args)
741
- check_if_method_has_arguments!(:left_outer_joins, args)
783
+ def left_outer_joins(*args)
784
+ check_if_method_has_arguments!(:left_outer_joins, args)
742
785
 
743
- args.compact!
744
- args.flatten!
786
+ args.compact!
787
+ args.flatten!
745
788
 
746
- spawn.left_outer_joins!(*args)
747
- end
789
+ spawn.left_outer_joins!(*args)
790
+ end
748
791
 
749
- def left_outer_joins!(*args) # :nodoc:
750
- self.left_outer_joins_values += args
751
- self
752
- end
792
+ def left_outer_joins!(*args) # :nodoc:
793
+ self.left_outer_joins_values += args
794
+ self
795
+ end
753
796
 
754
- def build_left_outer_joins(manager, outer_joins)
755
- buckets = outer_joins.group_by do |join|
756
- case join
757
- when Hash, Symbol, Array
758
- :association_join
759
- else
760
- raise ArgumentError, 'only Hash, Symbol and Array are allowed'
761
- end
797
+ def build_left_outer_joins(manager, outer_joins)
798
+ buckets = outer_joins.group_by do |join|
799
+ case join
800
+ when Hash, Symbol, Array
801
+ :association_join
802
+ else
803
+ raise ArgumentError, 'only Hash, Symbol and Array are allowed'
762
804
  end
763
-
764
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin)
765
805
  end
806
+
807
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin)
766
808
  end
767
809
  end
768
810
  # (End of left_outer_joins support)
@@ -784,7 +826,8 @@ ActiveSupport.on_load(:active_record) do
784
826
  end
785
827
 
786
828
  # Do this earlier because stuff here gets mixed into JoinDependency::JoinAssociation and AssociationScope
787
- if ActiveRecord.version < ::Gem::Version.new('5.0') && Object.const_defined?('PG::Connection')
829
+ if is_postgres && ActiveRecord.version < ::Gem::Version.new('5.0') # Was: && Object.const_defined?('PG::Connection')
830
+ require 'pg' # For ActiveRecord < 4.2
788
831
  # Avoid pg gem deprecation warning: "You should use PG::Connection, PG::Result, and PG::Error instead"
789
832
  PGconn = PG::Connection
790
833
  PGresult = PG::Result
@@ -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 the load on the database
180
- # # considerably, sometimes fixing what might appear to be an index page that just \"hangs\" for no apparent reason.
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.46
4
+ version: 1.0.49
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-07-15 00:00:00.000000000 Z
11
+ date: 2022-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -17,9 +17,6 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.2'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '7.2'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +24,6 @@ dependencies:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '4.2'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '7.2'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: fancy_gets
35
29
  requirement: !ruby/object:Gem::Requirement