brick 1.0.32 → 1.0.35

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: 8483d9a50013e440cce28f648d09d5322985702f952520cb83c69ad0e5742ce4
4
- data.tar.gz: 40072d0504f4d2b1cc9a59e62fef305b939dd977da0e794445adf003dbfb8b7e
3
+ metadata.gz: 34a23d1057771acea1beb9ea1ea2ff3d27e5983084bcd47f4c87686af77328d5
4
+ data.tar.gz: 44aea0d5fb92a361913ba4a17ebca62cf5be9aca5585fa9785b7c388c2652cee
5
5
  SHA512:
6
- metadata.gz: 9740778159acb464652322ef36eff763a55b38c6579fe0a7550a5797db44928ad6832a8bccc0d924a4d4c5a14dc02658e61bf4220cecff08fc348d540b67e41c
7
- data.tar.gz: 1b09db07a284512821b604f1d82f64c4be0c11db7f8a8973f36ab3f78b21804a7fec1eb641bcf1ac5c2ec118cb477c8086c124017c66550753ec4f9beddbd413
6
+ metadata.gz: e9124cbd0db47816db2dcbf59a76adcc0d4dcaea8f4ad02ea9ee99aace025c8914a7871c78a4a703f4daeced7dbaec12d52fdf56b6bc131c4c855be931d4f838
7
+ data.tar.gz: 671d0333b9a89870db67cdec05306311475724d674aab494c794baa0648736c873ea2aade170bdbc51642b96e4fe76a19f568bf31e724bd513230e1a78c69521
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/version'
4
+
5
+ # ActiveRecord before 4.0 didn't have #version
6
+ unless ActiveRecord.respond_to?(:version)
7
+ module ActiveRecord
8
+ def self.version
9
+ ::Gem::Version.new(ActiveRecord::VERSION::STRING)
10
+ end
11
+ end
12
+ end
13
+
14
+ # In ActiveSupport older than 5.0, the duplicable? test tries to new up a BigDecimal,
15
+ # and Ruby 2.6 and later deprecates #new. This removes the warning from BigDecimal.
16
+ # This compatibility needs to be put into place in the application's "config/boot.rb"
17
+ # file by having the line "require 'brick/compatibility'" to be the last line in that
18
+ # file.
19
+ require 'bigdecimal'
20
+ if ::Gem::Version.new(RUBY_VERSION) >= ::Gem::Version.new('2.6') &&
21
+ ActiveRecord.version < ::Gem::Version.new('5.0')
22
+ def BigDecimal.new(*args, **kwargs)
23
+ BigDecimal(*args, **kwargs)
24
+ end
25
+ end
@@ -86,7 +86,11 @@ module ActiveRecord
86
86
  descrip_col = (columns.map(&:name) - _brick_get_fks -
87
87
  (::Brick.config.metadata_columns || []) -
88
88
  [primary_key]).first
89
- dsl = ::Brick.config.model_descrips[name] = "[#{descrip_col}]" if descrip_col
89
+ dsl = ::Brick.config.model_descrips[name] = if descrip_col
90
+ "[#{descrip_col}]"
91
+ elsif (pk_parts = self.primary_key.is_a?(Array) ? self.primary_key : [self.primary_key])
92
+ "#{name} ##{pk_parts.map { |pk_part| "[#{pk_part}]" }.join(', ')}"
93
+ end
90
94
  end
91
95
  dsl
92
96
  end
@@ -129,7 +133,7 @@ module ActiveRecord
129
133
  end
130
134
  else # With no DSL available, still put this prefix into the JoinArray so we can get primary key (ID) info from this table
131
135
  x = prefix.each_with_object(build_array) { |v, s| s[v.to_sym] }
132
- x[prefix[-1]] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
136
+ x[prefix.last] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
133
137
  end
134
138
  members
135
139
  end
@@ -159,6 +163,7 @@ module ActiveRecord
159
163
  bracket_name.split('.').each do |part|
160
164
  obj_name += ".#{part}"
161
165
  this_obj = caches.fetch(obj_name) { caches[obj_name] = this_obj&.send(part.to_sym) }
166
+ break if this_obj.nil?
162
167
  end
163
168
  this_obj&.to_s || ''
164
169
  end
@@ -198,7 +203,9 @@ module ActiveRecord
198
203
  model_underscore = name.underscore
199
204
  assoc_name = CGI.escapeHTML(assoc_name.to_s)
200
205
  model_path = Rails.application.routes.url_helpers.send("#{model_underscore.tr('/', '_').pluralize}_path".to_sym)
201
- link = Class.new.extend(ActionView::Helpers::UrlHelper).link_to(name, model_path)
206
+ av_class = Class.new.extend(ActionView::Helpers::UrlHelper)
207
+ av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('6.1')
208
+ link = av_class.link_to(name, model_path)
202
209
  model_underscore == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
203
210
  end
204
211
 
@@ -252,13 +259,12 @@ module ActiveRecord
252
259
  names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
253
260
  else # "Normal" setup, fed from a JoinSource which has an array of JOINs
254
261
  # The left side is the "JOIN" table
255
- names += _recurse_arel(piece.left)
262
+ names += _recurse_arel(table = piece.left)
256
263
  # The expression on the right side is the "ON" clause
257
264
  # on = piece.right.expr
258
265
  # # Find the table which is not ourselves, and thus must be the "path" that led us here
259
266
  # parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
260
267
  # binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
261
- table = piece.left
262
268
  if table.is_a?(Arel::Nodes::TableAlias)
263
269
  alias_name = table.right
264
270
  table = table.left
@@ -277,7 +283,8 @@ module ActiveRecord
277
283
  @_brick_chains = {}
278
284
  # The left side is the "FROM" table
279
285
  # names += _recurse_arel(piece.left)
280
- names << [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)]
286
+ names << (this_name = [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)])
287
+ (_brick_chains[this_name.first] ||= []) << this_name.last
281
288
  # The right side is an array of all JOINs
282
289
  piece.right.each { |join| names << _recurse_arel(join) }
283
290
  end
@@ -304,6 +311,23 @@ module ActiveRecord
304
311
  # , is_add_bts, is_add_hms
305
312
  )
306
313
  is_add_bts = is_add_hms = true
314
+ is_distinct = nil
315
+ wheres = {}
316
+ params.each do |k, v|
317
+ case (ks = k.split('.')).length
318
+ when 1
319
+ next unless klass._brick_get_fks.include?(k)
320
+ when 2
321
+ assoc_name = ks.first.to_sym
322
+ # Make sure it's a good association name and that the model has that column name
323
+ next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
324
+
325
+ join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
326
+ is_distinct = true
327
+ distinct!
328
+ end
329
+ wheres[k] = v.split(',')
330
+ end
307
331
 
308
332
  # %%% Skip the metadata columns
309
333
  if selects&.empty? # Default to all columns
@@ -312,7 +336,9 @@ module ActiveRecord
312
336
  if (col_name = col.name) == 'class'
313
337
  col_alias = ' AS _class'
314
338
  end
315
- selects << "\"#{tbl_no_schema}\".\"#{col_name}\"#{col_alias}"
339
+ # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
340
+ cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col.name)&.first&.start_with?('xml')
341
+ selects << "\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
316
342
  end
317
343
  end
318
344
 
@@ -338,22 +364,6 @@ module ActiveRecord
338
364
  end
339
365
  end
340
366
 
341
- wheres = {}
342
- params.each do |k, v|
343
- case (ks = k.split('.')).length
344
- when 1
345
- next unless klass._brick_get_fks.include?(k)
346
- when 2
347
- assoc_name = ks.first.to_sym
348
- # Make sure it's a good association name and that the model has that column name
349
- next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
350
-
351
- join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
352
- distinct!
353
- end
354
- wheres[k] = v.split(',')
355
- end
356
-
357
367
  if join_array.present?
358
368
  left_outer_joins!(join_array)
359
369
  # Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
@@ -371,7 +381,9 @@ module ActiveRecord
371
381
  v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
372
382
  field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
373
383
 
374
- selects << "#{"\"#{field_tbl_name}\".\"#{sel_col.last}\""} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
384
+ # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
385
+ is_xml = is_distinct && Brick.relations[sel_col.first.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
386
+ selects << "\"#{field_tbl_name}\".\"#{sel_col.last}\"#{'::text' if is_xml} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
375
387
  v1[idx] << col_alias
376
388
  end
377
389
 
@@ -489,8 +501,9 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
489
501
  alias _brick_autoload_module! autoload_module!
490
502
  def autoload_module!(*args)
491
503
  into, const_name, qualified_name, path_suffix = args
492
- base_class_name = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)
493
- base_class_name = "::#{base_class_name}" unless base_class_name.start_with?('::')
504
+ if (base_class_name = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil))
505
+ base_class_name = "::#{base_class_name}" unless base_class_name.start_with?('::')
506
+ end
494
507
  if (base_class = base_class_name&.constantize)
495
508
  ::Brick.sti_models[qualified_name] = { base: base_class }
496
509
  # Build subclass and place it into the specially STI-namespaced module
@@ -548,7 +561,7 @@ Module.class_exec do
548
561
  full_class_name = +''
549
562
  full_class_name << "::#{self.name}" unless self == Object
550
563
  full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
551
- if (model = self.const_get(full_class_name))
564
+ if (plural_class_name == 'BrickSwagger' || model = self.const_get(full_class_name))
552
565
  # if it's a controller and no match or a model doesn't really use the same table name, eager load all models and try to find a model class of the right name.
553
566
  Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
554
567
  end
@@ -566,11 +579,13 @@ Module.class_exec do
566
579
  [built_module, "module #{schema_name}; end\n"]
567
580
  # # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
568
581
  elsif ::Brick.enable_models?
582
+ # Custom inheritable Brick base model?
583
+ class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick')
569
584
  # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
570
585
  # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
571
586
 
572
- if self != Object || # Are we in some namespace? ...
573
- (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil)&.constantize) # ... or part of an auto-STI namespace?
587
+ if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
588
+ self != Object # ... or otherwise already in some namespace?
574
589
  schema_name = [(singular_schema_name = name.underscore),
575
590
  (schema_name = singular_schema_name.pluralize),
576
591
  name,
@@ -583,7 +598,7 @@ Module.class_exec do
583
598
  if base_model
584
599
  schema_name = name.underscore # For the auto-STI namespace models
585
600
  table_name = base_model.table_name
586
- Object.send(:build_model, self, model_name, singular_table_name, table_name, relations, table_name)
601
+ Object.send(:build_model, self, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
587
602
  else
588
603
  # Adjust for STI if we know of a base model for the requested model name
589
604
  # %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
@@ -592,10 +607,13 @@ Module.class_exec do
592
607
  else
593
608
  ActiveSupport::Inflector.pluralize(singular_table_name)
594
609
  end
595
-
610
+ if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') &&
611
+ Apartment.excluded_models.include?(table_name.singularize.camelize)
612
+ schema_name = Apartment.default_schema
613
+ end
596
614
  # Maybe, just maybe there's a database table that will satisfy this need
597
615
  if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
598
- Object.send(:build_model, schema_name, model_name, singular_table_name, table_name, relations, matching)
616
+ Object.send(:build_model, schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
599
617
  end
600
618
  end
601
619
  end
@@ -623,9 +641,12 @@ class Object
623
641
 
624
642
  private
625
643
 
626
- def build_model(schema_name, model_name, singular_table_name, table_name, relations, matching)
627
- full_name = if schema_name.blank?
628
- model_name
644
+ def build_model(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
645
+ full_name = if (::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_name == Apartment.default_schema)
646
+ relation = relations["#{schema_name}.#{matching}"]
647
+ inheritable_name || model_name
648
+ elsif schema_name.blank?
649
+ inheritable_name || model_name
629
650
  else # Prefix the schema to the table name + prefix the schema namespace to the class name
630
651
  schema_module = if schema_name.instance_of?(Module) # from an auto-STI namespace?
631
652
  schema_name
@@ -633,21 +654,23 @@ class Object
633
654
  matching = "#{schema_name}.#{matching}"
634
655
  (Brick.db_schemas[schema_name] ||= self.const_get(schema_name.singularize.camelize))
635
656
  end
636
- "#{schema_module&.name}::#{model_name}"
657
+ "#{schema_module&.name}::#{inheritable_name || model_name}"
637
658
  end
638
659
 
639
- return if ((is_view = (relation = relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
660
+ return if ((is_view = (relation ||= relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
640
661
  ::Brick.config.exclude_tables.include?(matching)
641
662
 
642
663
  # Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
643
664
  if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
644
665
  unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
645
- puts "Warning: Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\"."
666
+ puts "Warning: Class name for a model that references table \"#{matching
667
+ }\" should be \"#{ActiveSupport::Inflector.singularize(inheritable_name || model_name)}\"."
646
668
  end
647
669
  return
648
670
  end
649
671
 
650
- if (base_model = ::Brick.sti_models[full_name]&.fetch(:base, nil) || ::Brick.existing_stis[full_name]&.constantize)
672
+ full_model_name = full_name.split('::').tap { |fn| fn[-1] = model_name }.join('::')
673
+ if (base_model = ::Brick.sti_models[full_model_name]&.fetch(:base, nil) || ::Brick.existing_stis[full_model_name]&.constantize)
651
674
  is_sti = true
652
675
  else
653
676
  base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
@@ -655,9 +678,21 @@ class Object
655
678
  hmts = nil
656
679
  code = +"class #{full_name} < #{base_model.name}\n"
657
680
  built_model = Class.new(base_model) do |new_model_class|
658
- (schema_module || Object).const_set(model_name.to_sym, new_model_class)
681
+ (schema_module || Object).const_set((inheritable_name || model_name).to_sym, new_model_class)
682
+ if inheritable_name
683
+ new_model_class.define_singleton_method :inherited do |subclass|
684
+ super(subclass)
685
+ if subclass.name == model_name
686
+ puts "#{full_model_name} properly extends from #{full_name}"
687
+ else
688
+ puts "should be \"class #{model_name} < #{inheritable_name}\"\n (not \"#{subclass.name} < #{inheritable_name}\")"
689
+ end
690
+ end
691
+ self.abstract_class = true
692
+ code << " self.abstract_class = true\n"
693
+ end
659
694
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
660
- code << " self.table_name = '#{self.table_name = matching}'\n" unless table_name == matching
695
+ code << " self.table_name = '#{self.table_name = matching}'\n" if inheritable_name || table_name != matching
661
696
 
662
697
  # Override models backed by a view so they return true for #is_view?
663
698
  # (Dynamically-created controllers and view templates for such models will then act in a read-only way)
@@ -685,6 +720,9 @@ class Object
685
720
  code << " self.primary_key = #{pk_sym.inspect}\n"
686
721
  end
687
722
  _brick_primary_key(relation) # Set the newly-found PK in the instance variable
723
+ elsif (possible_pk = ActiveRecord::Base.get_primary_key(base_class.name)) && relation[:cols][possible_pk]
724
+ new_model_class.primary_key = (possible_pk = possible_pk.to_sym)
725
+ code << " self.primary_key = #{possible_pk.inspect}\n"
688
726
  else
689
727
  code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
690
728
  end
@@ -725,7 +763,8 @@ class Object
725
763
  hmts&.each do |hmt_fk, fks|
726
764
  hmt_fk = hmt_fk.tr('.', '_')
727
765
  fks.each do |fk|
728
- through = fk.first[:assoc_name]
766
+ # %%% Will not work with custom has_many name
767
+ through = ::Brick.config.schema_behavior[:multitenant] ? fk.first[:assoc_name] : fk.first[:inverse_table].tr('.', '_').pluralize
729
768
  hmt_name = if fks.length > 1
730
769
  if fks[0].first[:inverse][:assoc_name] == fks[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
731
770
  "#{hmt_fk}_through_#{fk.first[:assoc_name]}"
@@ -740,8 +779,12 @@ class Object
740
779
  options = { through: through.to_sym }
741
780
  if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
742
781
  hmt_name = "#{hmt_name.singularize}_#{fk.first[:assoc_name]}"
743
- options[:class_name] = fk.first[:inverse_table].singularize.camelize
744
- options[:foreign_key] = fk.first[:fk].to_sym
782
+ # Was:
783
+ # options[:class_name] = fk.first[:inverse_table].singularize.camelize
784
+ # options[:foreign_key] = fk.first[:fk].to_sym
785
+ far_assoc = relations[fk.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == fk.last }
786
+ options[:class_name] = far_assoc.last[:inverse_table].singularize.camelize
787
+ options[:foreign_key] = far_assoc.last[:fk].to_sym
745
788
  end
746
789
  options[:source] = fk.last.to_sym unless hmt_name.singularize == fk.last
747
790
  code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
@@ -843,19 +886,70 @@ class Object
843
886
  def build_controller(namespace, class_name, plural_class_name, model, relations)
844
887
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
845
888
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
846
- pk = model._brick_primary_key(relations.fetch(table_name, nil))
889
+ pk = model&._brick_primary_key(relations.fetch(table_name, nil))
847
890
 
848
891
  namespace_name = "#{namespace.name}::" if namespace
849
892
  code = +"class #{namespace_name}#{class_name} < ApplicationController\n"
850
893
  built_controller = Class.new(ActionController::Base) do |new_controller_class|
851
894
  (namespace || Object).const_set(class_name.to_sym, new_controller_class)
852
895
 
853
- code << " def index\n"
854
- code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
855
- code << " @#{table_name}.brick_select(params)\n"
856
- code << " end\n"
896
+ unless (is_swagger = plural_class_name == 'BrickSwagger') # && request.format == :json)
897
+ code << " def index\n"
898
+ code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
899
+ code << " @#{table_name}.brick_select(params)\n"
900
+ code << " end\n"
901
+ end
857
902
  self.protect_from_forgery unless: -> { self.request.format.js? }
858
903
  self.define_method :index do
904
+ if is_swagger
905
+ json = { 'openapi': '3.0.1', 'info': { 'title': 'API V1', 'version': 'v1' },
906
+ 'servers': [
907
+ { 'url': 'https://{defaultHost}', 'variables': { 'defaultHost': { 'default': 'www.example.com' } } }
908
+ ]
909
+ }
910
+ json['paths'] = relations.inject({}) do |s, v|
911
+ s["/api/v1/#{v.first}"] = {
912
+ 'get': {
913
+ 'summary': "list #{v.first}",
914
+ 'parameters': v.last[:cols].map { |k, v| { 'name' => k, 'schema': { 'type': v.first } } },
915
+ 'responses': { '200': { 'description': 'successful' } }
916
+ }
917
+ }
918
+ # next if v.last[:isView]
919
+
920
+ s["/api/v1/#{v.first}/{id}"] = {
921
+ 'patch': {
922
+ 'summary': "update a #{v.first.singularize}",
923
+ 'parameters': v.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
924
+ { 'name' => k, 'schema': { 'type': v.first } }
925
+ end,
926
+ 'responses': { '200': { 'description': 'successful' } }
927
+ }
928
+ # "/api/v1/books/{id}": {
929
+ # "parameters": [
930
+ # {
931
+ # "name": "id",
932
+ # "in": "path",
933
+ # "description": "id",
934
+ # "required": true,
935
+ # "schema": {
936
+ # "type": "string"
937
+ # }
938
+ # },
939
+ # {
940
+ # "name": "Authorization",
941
+ # "in": "header",
942
+ # "schema": {
943
+ # "type": "string"
944
+ # }
945
+ # }
946
+ # ],
947
+ }
948
+ s
949
+ end
950
+ render inline: json.to_json, content_type: request.format
951
+ return
952
+ end
859
953
  ::Brick.set_db_schema(params)
860
954
  if request.format == :csv # Asking for a template?
861
955
  require 'csv'
@@ -869,7 +963,8 @@ class Object
869
963
  return
870
964
  end
871
965
 
872
- order = pk.each_with_object([]) { |pk_part, s| s << "#{model.table_name}.#{pk_part}" }
966
+ quoted_table_name = model.table_name.split('.').map { |x| "\"#{x}\"" }.join('.')
967
+ order = pk.each_with_object([]) { |pk_part, s| s << "#{quoted_table_name}.\"#{pk_part}\"" }
873
968
  ar_relation = order.present? ? model.order("#{order.join(', ')}") : model.all
874
969
  @_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
875
970
  # %%% Add custom HM count columns
@@ -884,7 +979,8 @@ class Object
884
979
  @_brick_join_array = join_array
885
980
  end
886
981
 
887
- if model.primary_key
982
+ is_pk_string = nil
983
+ if (pk_col = model&.primary_key)
888
984
  code << " def show\n"
889
985
  code << (find_by_id = " id = params[:id]&.split(/[\\/,_]/)
890
986
  id = id.first if id.is_a?(Array) && id.length == 1
@@ -892,14 +988,19 @@ class Object
892
988
  code << " end\n"
893
989
  self.define_method :show do
894
990
  ::Brick.set_db_schema(params)
895
- id = params[:id]&.split(/[\/,_]/)
991
+ id = if model.columns_hash[pk_col]&.type == :string
992
+ is_pk_string = true
993
+ params[:id]
994
+ else
995
+ params[:id]&.split(/[\/,_]/)
996
+ end
896
997
  id = id.first if id.is_a?(Array) && id.length == 1
897
998
  instance_variable_set("@#{singular_table_name}".to_sym, model.find(id))
898
999
  end
899
1000
  end
900
1001
 
901
1002
  # By default, views get marked as read-only
902
- unless false # model.readonly # (relation = relations[model.table_name]).key?(:isView)
1003
+ unless is_swagger # model.readonly # (relation = relations[model.table_name]).key?(:isView)
903
1004
  code << " # (Define :new, :create)\n"
904
1005
 
905
1006
  if model.primary_key
@@ -931,7 +1032,7 @@ class Object
931
1032
  # return
932
1033
  end
933
1034
 
934
- id = params[:id]&.split(/[\/,_]/)
1035
+ id = is_pk_string ? params[:id] : params[:id]&.split(/[\/,_]/)
935
1036
  id = id.first if id.is_a?(Array) && id.length == 1
936
1037
  instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(id)))
937
1038
  obj = obj.first if obj.is_a?(Array)
@@ -957,11 +1058,20 @@ class Object
957
1058
  end
958
1059
 
959
1060
  def _brick_get_hm_assoc_name(relation, hm_assoc)
960
- if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
1061
+ if (relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1) &&
1062
+ hm_assoc[:alternate_name] != hm_assoc[:inverse][:assoc_name]
961
1063
  plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
962
- [hm_assoc[:alternate_name] == name.underscore ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural, true]
1064
+ new_alt_name = (hm_assoc[:alternate_name] == name.underscore) ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural
1065
+ # uniq = 1
1066
+ # while same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }
1067
+ # hm_assoc[:assoc_name] = "#{hm_assoc_name}_#{uniq += 1}"
1068
+ # end
1069
+ # puts new_alt_name
1070
+ # hm_assoc[:assoc_name] = new_alt_name
1071
+ [new_alt_name, true]
963
1072
  else
964
1073
  assoc_name = hm_assoc[:inverse_table].pluralize
1074
+ # hm_assoc[:assoc_name] = assoc_name
965
1075
  [assoc_name, assoc_name.include?('.')]
966
1076
  end
967
1077
  end
@@ -989,6 +1099,12 @@ module ActiveRecord::ConnectionHandling
989
1099
  if File.exist?(brick_initializer = Rails.root.join('config/initializers/brick.rb'))
990
1100
  initializer_loaded = load brick_initializer
991
1101
  end
1102
+ # Load the initializer for the Apartment gem a little early so that if .excluded_models and
1103
+ # .default_schema are specified then we can work with non-tenanted models more appropriately
1104
+ if Object.const_defined?('Apartment') && File.exist?(apartment_initializer = Rails.root.join('config/initializers/apartment.rb'))
1105
+ load apartment_initializer
1106
+ apartment_excluded = Apartment.excluded_models
1107
+ end
992
1108
  # Only for Postgres? (Doesn't work in sqlite3)
993
1109
  # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
994
1110
 
@@ -1004,12 +1120,14 @@ module ActiveRecord::ConnectionHandling
1004
1120
  when 'Mysql2'
1005
1121
  ::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
1006
1122
  when 'SQLite'
1123
+ # %%% Retrieve internal ActiveRecord table names like this:
1124
+ # ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
1007
1125
  sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
1008
1126
  p.name AS column_name, p.type AS data_type,
1009
1127
  CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
1010
1128
  FROM sqlite_master AS m
1011
1129
  INNER JOIN pragma_table_info(m.name) AS p
1012
- WHERE m.name NOT IN ('ar_internal_metadata', 'schema_migrations')
1130
+ WHERE m.name NOT IN (?, ?)
1013
1131
  ORDER BY m.name, p.cid"
1014
1132
  else
1015
1133
  puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
@@ -1037,6 +1155,9 @@ module ActiveRecord::ConnectionHandling
1037
1155
  end
1038
1156
  end
1039
1157
 
1158
+ # %%% Retrieve internal ActiveRecord table names like this:
1159
+ # ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
1160
+ # For if it's not SQLite -- so this is the Postgres and MySQL version
1040
1161
  sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,
1041
1162
  c.column_name, c.data_type,
1042
1163
  COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
@@ -1058,18 +1179,22 @@ module ActiveRecord::ConnectionHandling
1058
1179
  WHERE t.table_schema NOT IN ('information_schema', 'pg_catalog')#{"
1059
1180
  AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }
1060
1181
  -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
1061
- AND t.table_name NOT IN ('pg_stat_statements', 'ar_internal_metadata', 'schema_migrations')
1182
+ AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
1062
1183
  ORDER BY 1, t.table_type DESC, c.ordinal_position"
1063
1184
  measures = []
1064
1185
  case ActiveRecord::Base.connection.adapter_name
1065
1186
  when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
1066
1187
  # schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1067
- ActiveRecord::Base.execute_sql(sql).each do |r|
1068
- relation_name = if ![schema, 'public'].include?(r['schema']) && !::Brick.config.schema_behavior[:multitenant]
1069
- "#{schema_name = r['schema']}.#{r['relation_name']}"
1070
- else
1071
- r['relation_name']
1072
- end
1188
+ ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
1189
+ ActiveRecord::Base.execute_sql(sql, ActiveRecord::Base.schema_migrations_table_name, ar_imtn).each do |r|
1190
+ # If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
1191
+ # is the default schema, usually 'public'.
1192
+ schema_name = if ::Brick.config.schema_behavior[:multitenant]
1193
+ Apartment.default_schema if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
1194
+ elsif ![schema, 'public'].include?(r['schema'])
1195
+ r['schema']
1196
+ end
1197
+ relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
1073
1198
  relation = relations[relation_name]
1074
1199
  relation[:isView] = true if r['table_type'] == 'VIEW'
1075
1200
  col_name = r['column_name']
@@ -1154,19 +1279,41 @@ module ActiveRecord::ConnectionHandling
1154
1279
  # ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1155
1280
  ActiveRecord::Base.execute_sql(sql).each do |fk|
1156
1281
  fk = fk.values unless fk.is_a?(Array)
1157
- # Multitenancy makes things a little more general overall
1158
- fk[0] = nil if fk[0] == 'public' || (is_multitenant && fk[0] == schema)
1159
- fk[3] = nil if fk[3] == 'public' || (is_multitenant && fk[3] == schema)
1282
+ # Multitenancy makes things a little more general overall, except for non-tenanted tables
1283
+ if apartment_excluded&.include?(fk[1].singularize.camelize)
1284
+ fk[0] = Apartment.default_schema
1285
+ elsif fk[0] == 'public' || (is_multitenant && fk[0] == schema)
1286
+ fk[0] = nil
1287
+ end
1288
+ if apartment_excluded&.include?(fk[4].singularize.camelize)
1289
+ fk[3] = Apartment.default_schema
1290
+ elsif fk[3] == 'public' || (is_multitenant && fk[3] == schema)
1291
+ fk[3] = nil
1292
+ end
1160
1293
  ::Brick._add_bt_and_hm(fk, relations)
1161
1294
  end
1162
1295
  end
1163
1296
  end
1164
1297
 
1165
- puts "\nClasses that can be built from tables:"
1166
- relations.select { |_k, v| !v.key?(:isView) }.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
1167
- unless (views = relations.select { |_k, v| v.key?(:isView) }).empty?
1298
+ apartment = Object.const_defined?('Apartment') && Apartment
1299
+ tables = []
1300
+ views = []
1301
+ relations.each do |k, v|
1302
+ name_parts = k.split('.')
1303
+ if v.key?(:isView)
1304
+ views
1305
+ else
1306
+ name_parts.shift if apartment && name_parts.length > 1 && name_parts.first == Apartment.default_schema
1307
+ tables
1308
+ end << name_parts.map { |x| x.singularize.camelize }.join('::')
1309
+ end
1310
+ unless tables.empty?
1311
+ puts "\nClasses that can be built from tables:"
1312
+ tables.sort.each { |x| puts x }
1313
+ end
1314
+ unless views.empty?
1168
1315
  puts "\nClasses that can be built from views:"
1169
- views.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
1316
+ views.sort.each { |x| puts x }
1170
1317
  end
1171
1318
 
1172
1319
  ::Brick.load_additional_references if initializer_loaded
@@ -1209,16 +1356,31 @@ module Brick
1209
1356
  end
1210
1357
  # %%% Temporary schema patch
1211
1358
  for_tbl = fk[1]
1359
+ apartment = Object.const_defined?('Apartment') && Apartment
1360
+ fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(for_tbl.singularize.camelize)
1212
1361
  fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
1213
1362
  bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
1363
+
1214
1364
  # %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
1215
1365
  # Maybe it's already gotten this info because we got as far as to say there was a unique class
1216
1366
  primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
1217
1367
  pri_tbl = (primary_class = fk[4][:class].constantize).table_name
1368
+ if (pri_tbl_parts = pri_tbl.split('.')).length > 1
1369
+ fk[3] = pri_tbl_parts.first
1370
+ end
1218
1371
  else
1219
- is_schema = fk[3] != ::Brick.default_schema && (::Brick.config.schema_behavior[:multitenant] || fk[3] != 'public')
1372
+ is_schema = if ::Brick.config.schema_behavior[:multitenant]
1373
+ # If Apartment gem lists the primary table as being associated with a non-tenanted model
1374
+ # then use 'public' schema for the primary table
1375
+ if apartment && apartment&.excluded_models.include?(fk[4].singularize.camelize)
1376
+ fk[3] = Apartment.default_schema
1377
+ true
1378
+ end
1379
+ else
1380
+ fk[3] && fk[3] != ::Brick.default_schema && fk[3] != 'public'
1381
+ end
1220
1382
  pri_tbl = fk[4]
1221
- fk[3] && is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
1383
+ is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
1222
1384
  end
1223
1385
  hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
1224
1386
 
@@ -1287,7 +1449,13 @@ module Brick
1287
1449
  assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
1288
1450
  assoc_hm[:inverse] = assoc_bt
1289
1451
  else
1290
- assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: for_tbl.pluralize, alternate_name: bt_assoc_name, inverse_table: fk[1], inverse: assoc_bt }
1452
+ inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == Apartment.default_schema
1453
+ for_tbl
1454
+ else
1455
+ fk[1]
1456
+ end
1457
+ assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: for_tbl.pluralize, alternate_name: bt_assoc_name,
1458
+ inverse_table: inv_tbl, inverse: assoc_bt }
1291
1459
  assoc_hm[:polymorphic] = true if is_polymorphic
1292
1460
  hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
1293
1461
  hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
@@ -124,8 +124,15 @@ module Brick
124
124
  schema_options = ::Brick.db_schemas.keys.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
125
125
  # %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
126
126
  # environment or whatever, then get either the controllers or routes list instead
127
- table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).sort
128
- .each_with_object(+'') { |v, s| s << "<option value=\"#{v.underscore.gsub('.', '/').pluralize}\">#{v}</option>" }.html_safe
127
+ apartment_default_schema = ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && Apartment.default_schema
128
+ table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).map do |tbl|
129
+ if (tbl_parts = tbl.split('.')).first == apartment_default_schema
130
+ tbl = tbl_parts.last
131
+ end
132
+ tbl
133
+ end.sort.each_with_object(+'') do |v, s|
134
+ s << "<option value=\"#{v.underscore.gsub('.', '/').pluralize}\">#{v}</option>"
135
+ end.html_safe
129
136
  css = +"<style>
130
137
  #dropper {
131
138
  background-color: #eee;
@@ -209,12 +216,17 @@ input[type=submit] {
209
216
  <% def is_bcrypt?(val)
210
217
  val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
211
218
  end
212
- def hide_bcrypt(val)
219
+ def hide_bcrypt(val, max_len = 200)
213
220
  if is_bcrypt?(val)
214
221
  '(hidden)'
215
- elsif val.is_a?(String) && val.encoding.name != 'UTF-8'
216
- val[0..1000].force_encoding('UTF-8')
217
222
  else
223
+ if val.is_a?(String)
224
+ if val.length > max_len
225
+ val = val[0...max_len]
226
+ val << '...'
227
+ end
228
+ val.force_encoding('UTF-8') unless val.encoding.name == 'UTF-8'
229
+ end
218
230
  val
219
231
  end
220
232
  end %>"
@@ -238,46 +250,51 @@ end %>"
238
250
  poly_cols = #{poly_cols.inspect} %>"
239
251
  end
240
252
 
241
- # %%% When doing schema select, if there's an ID then remove it, or if we're on a new page go to index
253
+ # %%% When doing schema select, if we're on a new page go to index
242
254
  script = "<script>
243
255
  var schemaSelect = document.getElementById(\"schema\");
256
+ var tblSelect = document.getElementById(\"tbl\");
244
257
  var brickSchema;
245
- if (schemaSelect) {
246
- brickSchema = changeout(location.href, \"_brick_schema\");
247
- if (brickSchema) {
248
- [... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
258
+
259
+ // This PageTransitionEvent fires when the page first loads, as well as after any other history
260
+ // transition such as when using the browser's Back and Forward buttons.
261
+ window.addEventListener(\"pageshow\", function() {
262
+ if (schemaSelect) { // First drop-down is only present if multitenant
263
+ brickSchema = changeout(location.href, \"_brick_schema\");
264
+ if (brickSchema) {
265
+ [... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
266
+ }
267
+ schemaSelect.value = brickSchema || \"public\";
268
+ schemaSelect.focus();
269
+ schemaSelect.addEventListener(\"change\", function () {
270
+ // If there's an ID then remove it (trim after selected table)
271
+ location.href = changeout(location.href, \"_brick_schema\", this.value, tblSelect.value);
272
+ });
249
273
  }
250
- schemaSelect.value = brickSchema || \"public\";
251
- schemaSelect.focus();
252
- schemaSelect.addEventListener(\"change\", function () {
253
- location.href = changeout(location.href, \"_brick_schema\", this.value);
254
- });
255
- }
256
- [... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
257
- if (brickSchema)
258
- form.action = changeout(form.action, \"_brick_schema\", brickSchema);
259
- form.addEventListener('submit', function (ev) {
260
- [... ev.target.getElementsByTagName(\"SELECT\")].forEach(function (select) {
261
- if (select.value === \"^^^brick_NULL^^^\")
262
- select.value = null;
274
+ [... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
275
+ if (brickSchema)
276
+ form.action = changeout(form.action, \"_brick_schema\", brickSchema);
277
+ form.addEventListener('submit', function (ev) {
278
+ [... ev.target.getElementsByTagName(\"SELECT\")].forEach(function (select) {
279
+ if (select.value === \"^^^brick_NULL^^^\")
280
+ select.value = null;
281
+ });
282
+ return true;
263
283
  });
264
- return true;
265
284
  });
266
- });
267
285
 
268
- var tblSelect = document.getElementById(\"tbl\");
269
- if (tblSelect) {
270
- tblSelect.value = changeout(location.href)[0];
271
- if (tblSelect.selectedIndex < 0) tblSelect.value = changeout(location.href)[1];
272
- tblSelect.addEventListener(\"change\", function () {
273
- var lhr = changeout(location.href, null, this.value);
274
- if (brickSchema)
275
- lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
276
- location.href = lhr;
277
- });
278
- }
286
+ if (tblSelect) { // Always present
287
+ tblSelect.value = changeout(location.href)[schemaSelect ? 1 : 0];
288
+ tblSelect.addEventListener(\"change\", function () {
289
+ var lhr = changeout(location.href, null, this.value);
290
+ if (brickSchema)
291
+ lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
292
+ location.href = lhr;
293
+ });
294
+ }
295
+ });
279
296
 
280
- function changeout(href, param, value) {
297
+ function changeout(href, param, value, trimAfter) {
281
298
  var hrefParts = href.split(\"?\");
282
299
  if (param === undefined || param === null) {
283
300
  hrefParts = hrefParts[0].split(\"://\");
@@ -288,6 +305,11 @@ function changeout(href, param, value) {
288
305
  else
289
306
  return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
290
307
  }
308
+ if (trimAfter) {
309
+ var pathParts = hrefParts[0].split(\"/\");
310
+ while (pathParts.lastIndexOf(trimAfter) != pathParts.length - 1) pathParts.pop();
311
+ hrefParts[0] = pathParts.join(\"/\");
312
+ }
291
313
  var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
292
314
  params = params.reduce(function (s, v) { var parts = v.split(\"=\"); s[parts[0]] = parts[1]; return s; }, {});
293
315
  if (value === undefined) return params[param];
@@ -353,7 +375,6 @@ function changeout(href, param, value) {
353
375
 
354
376
  async function updateSignInStatus(isSignedIn) {
355
377
  if (isSignedIn) {
356
- console.log(\"turds!\");
357
378
  await gapi.client.sheets.spreadsheets.create({
358
379
  properties: {
359
380
  title: #{table_name.inspect},
@@ -407,7 +428,7 @@ function changeout(href, param, value) {
407
428
  origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
408
429
  # binding.pry
409
430
  if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| puts fk.inspect; fk[:fk] == key_parts.last }) &&
410
- (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
431
+ (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
411
432
  <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.name.underscore.tr('/', '_')\}_path\".to_sym, id) %></h3><%
412
433
  end
413
434
  end %>
@@ -442,7 +463,6 @@ function changeout(href, param, value) {
442
463
  ::Brick.config.metadata_columns.include?(k) || poly_cols.include?(k) || k.start_with?('_brfk_') || (k.start_with?('_br_') && (k.length == 63 || k.end_with?('_ct'))) %>
443
464
  <td>
444
465
  <% if (bt = bts[k]) %>
445
- <%# binding.pry # Postgres column names are limited to 63 characters %>
446
466
  <% if bt[2] # Polymorphic?
447
467
  bt_class = #{obj_name}.send(\"#\{bt.first\}_type\")
448
468
  base_class = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class.name.underscore
@@ -451,6 +471,7 @@ function changeout(href, param, value) {
451
471
  send(\"#\{base_class\}_path\".to_sym, poly_id)) if poly_id %><%
452
472
  else
453
473
  bt_txt = (bt_class = bt[1].first.first).brick_descrip(
474
+ # 0..62 because Postgres column names are limited to 63 characters
454
475
  #{obj_name}, (descrips = @_brick_bt_descrip[bt.first][bt_class])[0..-2].map { |z| #{obj_name}.send(z.last[0..62]) }, (bt_id_col = descrips.last)
455
476
  )
456
477
  bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
@@ -533,7 +554,7 @@ function changeout(href, param, value) {
533
554
  <% else case #{model_name}.column_for_attribute(k).type
534
555
  when :string, :text %>
535
556
  <% if is_bcrypt?(val) # || .readonly? %>
536
- <%= hide_bcrypt(val) %>
557
+ <%= hide_bcrypt(val, 1000) %>
537
558
  <% else %>
538
559
  <div class=\"wide-input\"><%= f.text_field k.to_sym %></div>
539
560
  <% end %>
@@ -566,7 +587,7 @@ function changeout(href, param, value) {
566
587
  s << "<table id=\"#{hm_name}\">
567
588
  <tr><th>#{hm[3]}</th></tr>
568
589
  <% collection = @#{obj_name}.#{hm_name}
569
- collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection]
590
+ collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection].compact
570
591
  if collection.empty? %>
571
592
  <tr><td>(none)</td></tr>
572
593
  <% else %>
@@ -600,6 +621,7 @@ function changeout(href, param, value) {
600
621
 
601
622
  # Just in case it hadn't been done previously when we tried to load the brick initialiser,
602
623
  # go make sure we've loaded additional references (virtual foreign keys and polymorphic associations).
624
+ # (This should only happen if for whatever reason the initializer file was not exactly config/initializers/brick.rb.)
603
625
  ::Brick.load_additional_references
604
626
  end
605
627
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 32
8
+ TINY = 35
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
@@ -1,25 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record/version'
4
-
5
- # ActiveRecord before 4.0 didn't have #version
6
- unless ActiveRecord.respond_to?(:version)
7
- module ActiveRecord
8
- def self.version
9
- ::Gem::Version.new(ActiveRecord::VERSION::STRING)
10
- end
11
- end
12
- end
13
-
14
- # In ActiveSupport older than 5.0, the duplicable? test tries to new up a BigDecimal,
15
- # and Ruby 2.6 and later deprecates #new. This removes the warning from BigDecimal.
16
- require 'bigdecimal'
17
- if (ruby_version = ::Gem::Version.new(RUBY_VERSION)) >= ::Gem::Version.new('2.6') &&
18
- ActiveRecord.version < ::Gem::Version.new('5.0')
19
- def BigDecimal.new(*args, **kwargs)
20
- BigDecimal(*args, **kwargs)
21
- end
22
- end
3
+ require 'brick/compatibility'
23
4
 
24
5
  # Allow ActiveRecord 4.0 and 4.1 to work with newer Ruby (>= 2.4) by avoiding a "stack level too deep"
25
6
  # error when ActiveSupport tries to smarten up Numeric by messing with Fixnum and Bignum at the end of:
@@ -45,8 +26,8 @@ end
45
26
  require 'brick/util'
46
27
 
47
28
  # Allow ActiveRecord < 3.2 to work with Ruby 2.7 and later
48
- if ActiveRecord.version < ::Gem::Version.new('3.2') &&
49
- ruby_version >= ::Gem::Version.new('2.7')
29
+ if (ruby_version = ::Gem::Version.new(RUBY_VERSION)) >= ::Gem::Version.new('2.7') &&
30
+ ActiveRecord.version < ::Gem::Version.new('3.2')
50
31
  # Remove circular reference for "now"
51
32
  ::Brick::Util._patch_require(
52
33
  'active_support/values/time_zone.rb', '/activesupport',
@@ -409,6 +390,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
409
390
  ::Brick.relations.each do |rel_name, v|
410
391
  rel_name = rel_name.split('.').map(&:underscore)
411
392
  schema_names = rel_name[0..-2]
393
+ schema_names.shift if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_names.first == Apartment.default_schema
412
394
  k = rel_name.last
413
395
  unless existing_controllers.key?(controller_name = k.pluralize)
414
396
  options = {}
@@ -422,6 +404,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
422
404
  end
423
405
  end
424
406
  end
407
+ send(:get, '/api-docs/v1/swagger.json', { to: 'brick_swagger#index' }) if Object.const_defined?('Rswag::Ui')
425
408
  end
426
409
  super
427
410
  end
@@ -18,7 +18,8 @@ module Brick
18
18
  desc 'Generates an initializer file for configuring Brick'
19
19
 
20
20
  def create_initializer_file
21
- unless File.exist?(filename = 'config/initializers/brick.rb')
21
+ is_brick_file = File.exist?(filename = 'config/initializers/brick.rb')
22
+ if is_brick_file && ::Brick.config.schema_behavior[:multitenant] || !is_brick_file
22
23
  # See if we can make suggestions for additional_references and polymorphic associations
23
24
  resembles_fks = Hash.new { |h, k| h[k] = [] }
24
25
  possible_polymorphics = {}
@@ -28,7 +29,7 @@ module Brick
28
29
  col_down = col.downcase
29
30
 
30
31
  if (is_possible_poly = ['character varying', 'text'].include?(type.first))
31
- if col_down.end_with?('_type') &&
32
+ if col_down.end_with?('_type')
32
33
  poly_type_cut_length = -6
33
34
  col_down = col_down[0..-6]
34
35
  elsif col_down.end_with?('type')
@@ -223,7 +224,10 @@ module Brick
223
224
  # # Database schema to use when analysing existing data, such as deriving a list of polymorphic classes in the case that
224
225
  # # it wasn't originally specified.
225
226
  # Brick.schema_behavior = :namespaced
226
- # Brick.schema_behavior = { multitenant: { schema_to_analyse: 'engineering' } }
227
+ #{Brick.config.schema_behavior ? "Brick.schema_behavior = { multitenant: { schema_to_analyse: #{
228
+ Brick.config.schema_behavior[:multitenant][:schema_to_analyse].inspect}" :
229
+ "# Brick.schema_behavior = { multitenant: { schema_to_analyse: 'engineering'"
230
+ } } }
227
231
 
228
232
  # # Polymorphic associations are set up by providing a model name and polymorphic association name#{poly}
229
233
 
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.32
4
+ version: 1.0.35
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-06-08 00:00:00.000000000 Z
11
+ date: 2022-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -214,6 +214,7 @@ extensions: []
214
214
  extra_rdoc_files: []
215
215
  files:
216
216
  - lib/brick.rb
217
+ - lib/brick/compatibility.rb
217
218
  - lib/brick/config.rb
218
219
  - lib/brick/extensions.rb
219
220
  - lib/brick/frameworks/cucumber.rb