Module: Brick

Defined in:
lib/brick/util.rb,
lib/brick.rb,
lib/brick/config.rb,
lib/brick/join_array.rb,
lib/brick/version_number.rb,
lib/brick/serializers/json.rb,
lib/brick/serializers/yaml.rb,
lib/brick/frameworks/cucumber.rb,
lib/brick/frameworks/rails/engine.rb,
lib/generators/brick/seeds_generator.rb,
lib/generators/brick/models_generator.rb,
lib/generators/brick/install_generator.rb,
lib/generators/brick/migrations_generator.rb,
lib/brick/extensions.rb

Overview

:nodoc:

Defined Under Namespace

Modules: Cucumber, Extensions, Rails, RouteSet, Serializers, Util, VERSION Classes: Config, InstallGenerator, JoinArray, JoinHash, MigrationsGenerator, ModelsGenerator, SeedsGenerator

Constant Summary collapse

ALL_API_ACTIONS =
[:index, :show, :create, :update, :destroy]
CURRENCY_SYMBOLS =
'$£¢₵€₠ƒ¥₿₩₪₹₫₴₱₲₳₸₺₼₽៛₡₢₣₤₥₦₧₨₭₮₯₰₶₷₻₾'
ADD_CONST_MISSING =
lambda do
  alias _brick_const_missing const_missing
  def const_missing(*args)
    requested = args.first.to_s
    is_controller = requested.end_with?('Controller')
    # self.name is nil when a model name is requested in an .erb file
    if self.name && ::Brick.config.path_prefix
      split_self_name.shift if (split_self_name = self.name.split('::')).first.blank?
      # Asking for the prefix module?
      camelize_prefix = ::Brick.config.path_prefix.camelize
      if self == Object && requested == camelize_prefix
        Object.const_set(args.first, (built_module = Module.new))
        puts "module #{camelize_prefix}; end\n"
        return built_module
      elsif module_parent == Object && self.name == camelize_prefix ||
            module_parent.name == camelize_prefix && module_parent.module_parent == Object
        split_self_name.shift # Remove the identified path prefix from the split name
        is_brick_prefix = true
        if is_controller
          brick_root = split_self_name.empty? ? self : camelize_prefix.constantize
        end
      end
    end
    base_module = if self < ActiveRecord::Migration || !self.name
                    brick_root || Object
                  elsif (split_self_name || self.name.split('::')).length > 1 # Classic mode
                    begin
                      return self._brick_const_missing(*args)

                    rescue NameError # %%% Avoid the error "____ cannot be autoloaded from an anonymous class or module"
                      return self.const_get(args.first) if self.const_defined?(args.first)

                      # unless self == (prnt = (respond_to?(:parent) ? parent : module_parent))
                      unless self == Object
                        begin
                          return Object._brick_const_missing(*args)

                        rescue NameError
                          return Object.const_get(args.first) if Object.const_defined?(args.first)

                        end
                      end
                    end
                    Object
                  else
                    sti_base = (::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::#{requested}", nil) ||
                                ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil))&.constantize
                    self
                  end
    # puts "#{self.name} - #{args.first}"
    # Unless it's a Brick prefix looking for a TNP that should create a module ...
    unless (is_tnp_module = (is_brick_prefix && !is_controller && ::Brick.config.table_name_prefixes.values.include?(requested)))
      # ... first look around for an existing module or class.
      desired_classname = (self == Object || !name) ? requested : "#{name}::#{requested}"
      if ((is_defined = self.const_defined?(args.first)) && (possible = self.const_get(args.first)) &&
          # Reset `possible` if it's a controller request that's not a perfect match
          # Was:  (possible = nil)  but changed to #local_variable_set in order to suppress the "= should be ==" warning
          (possible&.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) ||
         # Try to require the respective Ruby file
         ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
                      (self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
          ) && (require_dependency(filename) || true) &&
          ((possible = self.const_get(args.first)) && possible.name == desired_classname)
         ) ||
         # If any class has turned up so far (and we're not in the middle of eager loading)
         # then return what we've found.
         (is_defined && !::Brick.is_eager_loading) # Used to also have:   && possible != self
        if ((!brick_root && (filename || possible.instance_of?(Class))) ||
            (possible.instance_of?(Module) && possible&.module_parent == self) ||
            (possible.instance_of?(Class) && possible == self)) && # Are we simply searching for ourselves?
           # Skip when what we found as `possible` is not related to the base class of an STI model
           (!sti_base || possible.is_a?(sti_base))
          return possible
        end
      end
    end
    class_name = ::Brick.namify(requested)
    relations = ::Brick.relations
    #        CONTROLLER
    result = if ::Brick.enable_controllers? &&
                is_controller && (plural_class_name = class_name[0..-11]).length.positive?
               # Otherwise now it's up to us to fill in the gaps
               controller_class_name = +''
               full_class_name = +''
               unless self == Object
                 controller_class_name << ((split_self_name&.first && split_self_name.join('::')) || self.name)
                 full_class_name << "::#{controller_class_name}"
                 controller_class_name << '::'
               end
               # (Go over to underscores for a moment so that if we have something come in like VABCsController then the model name ends up as
               # Vabc instead of VABC)
               singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize
               full_class_name << "::#{singular_class_name}"
               skip_controller = nil
               if plural_class_name == 'BrickOpenapi' ||
                  (
                    (::Brick.config.add_status || ::Brick.config.add_orphans) &&
                    plural_class_name == 'BrickGem'
                  ) ||
                 begin
                   model = self.const_get(full_class_name)

                   # In the very rare case that we've picked up a MODULE which has the same name as what would be the
                   # resource's MODEL name, just build out an appropriate auto-model on-the-fly. (RailsDevs code has this in PayCustomer.)
                   # %%% We don't yet display the code for this new model
                   if model && !model.is_a?(Class)
                     model, _code = Object.send(:build_model, relations, model.module_parent, model.module_parent.name, singular_class_name)
                   end
                 rescue NameError # If the const_get for the model has failed...
                   skip_controller = true
                   # ... then just fall through and allow it to fail when trying to loading the ____Controller class normally.
                 end
               end
               unless skip_controller
                 Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
               end

             # MODULE
             elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
                   # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing
                   (base_module == Object || (camelize_prefix && base_module == Object.const_get(camelize_prefix))) &&
                   (schema_name = [(singular_table_name = class_name.underscore),
                                   (table_name = singular_table_name.pluralize),
                                   ::Brick.is_oracle ? class_name.upcase : class_name,
                                   (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas&.include?(s) }&.camelize ||
                                  (::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name) ||
                                  (::Brick.config.table_name_prefixes&.values&.include?(class_name) && class_name))
               return self.const_get(schema_name) if !is_tnp_module && self.const_defined?(schema_name)

               # Build out a module for the schema if it's namespaced
               # schema_name = schema_name.camelize
               base_module.const_set(schema_name.to_sym, (built_module = Module.new))

               [built_module, "module #{schema_name}; end\n"]
               # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied

             # AVO Resource
             elsif base_module == Object && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && requested.end_with?('Resource') &&
                   # Expect that anything called MotorResource or SpinaResource could be from those administrative gems
                   requested.length > 8 && ['MotorResource', 'SpinaResource'].exclude?(requested)
               if (model = Object.const_get(requested[0..-9])) && model < ActiveRecord::Base
                 require 'generators/avo/resource_generator'
                 field_generator = Generators::Avo::ResourceGenerator.new([''])
                 field_generator.instance_variable_set(:@model, model)
                 fields = field_generator.send(:generate_fields)&.split("\n")
                                         &.each_with_object([]) do |f, s|
                                           if (f = f.strip).start_with?('field ')
                                             f = f[6..-1].split(',')
                                             s << [f.first[1..-1].to_sym, [f[1][1..-1].split(': :').map(&:to_sym)].to_h]
                                           end
                                         end || []
                 built_resource = Class.new(Avo::BaseResource) do |new_resource_class|
                   self.model_class = model
                   self.title = :brick_descrip
                   self.includes = []
                   if (!model.is_view? && mod_pk = model.primary_key)
                     field((mod_pk.is_a?(Array) ? mod_pk.first : mod_pk).to_sym, { as: :id })
                   end
                   # Create a call such as:  field :name, as: :text
                   fields.each do |f|
                     # Add proper types if this is a polymorphic belongs_to
                     if f.last == { as: :belongs_to } &&
                        (fk = ::Brick.relations[model.table_name][:fks].find { |k, v| v[:assoc_name] == f.first.to_s }) &&
                        fk.last.fetch(:polymorphic, nil)
                       poly_types = fk.last.fetch(:inverse_table, nil)&.each_with_object([]) do |poly_table, s|
                         s << Object.const_get(::Brick.relations[poly_table][:class_name])
                       end
                       if poly_types.present?
                         f.last[:polymorphic_as] = f.first
                         f.last[:types] = poly_types
                       end
                     end
                     self.send(:field, *f)
                   end
                 end
                 Object.const_set(requested.to_sym, built_resource)
                 [built_resource, nil]
               end

             # MODEL
             elsif ::Brick.enable_models?
               # Custom inheritable Brick base model?
               class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick')
               Object.send(:build_model, relations, base_module, name, class_name, inheritable_name)
             end
    if result
      built_class, code = result
      puts "\n#{code}\n"
      built_class
    elsif !schema_name && ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}")
#         module_prefixes = type_name.split('::')
#         path = base_module.name.split('::')[0..-2] + []
#         module_prefixes.unshift('') unless module_prefixes.first.blank?
#         candidate_file = ::Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
      base_module._brick_const_missing(*args)
    # elsif base_module != Object
    #   module_parent.const_missing(*args)
    elsif Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
          (Rails::Autoloaders.respond_to?(:zeitwerk_enabled?) ? Rails::Autoloaders.zeitwerk_enabled? : true)
      self._brick_const_missing(*args) # ... rely solely on Zeitwerk.
    else # Classic mode
      unless (found = base_module._brick_const_missing(*args))
        puts "MISSING! #{base_module.name} #{args.inspect} #{table_name}"
      end
      found
    end
  end

  # Support Rails < 6.0 which adds #parent instead of #module_parent
  unless respond_to?(:module_parent)
    def module_parent # Weirdly for Grape::API does NOT come in with the proper class, but some anonymous Class thing
      parent
    end
  end
end

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.auto_modelsObject

Returns the value of attribute auto_models.



109
110
111
# File 'lib/brick.rb', line 109

def auto_models
  @auto_models
end

.db_schemasObject

Returns the value of attribute db_schemas.



109
110
111
# File 'lib/brick.rb', line 109

def db_schemas
  @db_schemas
end

.default_schemaObject

Returns the value of attribute default_schema.



109
110
111
# File 'lib/brick.rb', line 109

def default_schema
  @default_schema
end

.initializer_loadedObject

Returns the value of attribute initializer_loaded.



109
110
111
# File 'lib/brick.rb', line 109

def initializer_loaded
  @initializer_loaded
end

.is_eager_loadingObject

Returns the value of attribute is_eager_loading.



109
110
111
# File 'lib/brick.rb', line 109

def is_eager_loading
  @is_eager_loading
end

.is_oracleObject

Returns the value of attribute is_oracle.



109
110
111
# File 'lib/brick.rb', line 109

def is_oracle
  @is_oracle
end

.routes_doneObject

Returns the value of attribute routes_done.



109
110
111
# File 'lib/brick.rb', line 109

def routes_done
  @routes_done
end

.test_schemaObject

Returns the value of attribute test_schema.



109
110
111
# File 'lib/brick.rb', line 109

def test_schema
  @test_schema
end

Class Method Details

._add_bt_and_hm(fk, relations, polymorphic_class = nil, is_optional = false) ⇒ Object



2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
# File 'lib/brick/extensions.rb', line 2918

def _add_bt_and_hm(fk, relations, polymorphic_class = nil, is_optional = false)
  bt_assoc_name = ::Brick.namify(fk[2].dup, :downcase)
  unless polymorphic_class
    bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
                      bt_assoc_name[-3] == '_' ? bt_assoc_name[0..-4] : bt_assoc_name[0..-3]
                    elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_')
                      bt_assoc_name[0..-3] # Make the bold assumption that we can just peel off any final ID part
                    else
                      "#{bt_assoc_name}_bt"
                    end
  end
  bt_assoc_name = "#{bt_assoc_name}_" if bt_assoc_name == 'attribute'

  # %%% Temporary schema patch
  for_tbl = fk[1]
  fk_namified = ::Brick.namify(fk[1])
  fk[0] = ::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(fk_namified)
  fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
  bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }

  # %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
  # Maybe it's already gotten this info because we got as far as to say there was a unique class
  primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
                    pri_tbl = (primary_class = fk[4][:class].constantize).table_name
                    if (pri_tbl_parts = pri_tbl.split('.')).length > 1
                      fk[3] = pri_tbl_parts.first
                    end
                  else
                    is_schema = if ::Brick.config.schema_behavior[:multitenant]
                                  # If Apartment gem lists the primary table as being associated with a non-tenanted model
                                  # then use 'public' schema for the primary table
                                  if ::Brick.is_apartment_excluded_table(fk[4])
                                    fk[3] = ::Brick.apartment_default_tenant
                                    true
                                  end
                                else
                                  fk[3] && fk[3] != ::Brick.default_schema && fk[3] != 'public'
                                end
                    pri_tbl = fk[4]
                    is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
                  end
  hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class

  unless (cnstr_name = fk[5])
    # For any appended references (those that come from config), arrive upon a definitely unique constraint name
    pri_tbl = is_class ? fk[4][:class].underscore : pri_tbl
    pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl&.singularize != bt_assoc_name
    cnstr_name = ensure_unique(+"(brick) #{for_tbl}_#{pri_tbl}", bts, hms)
    missing = []
    missing << fk[1] unless relations.key?(fk[1])
    missing << primary_table unless is_class || relations.key?(primary_table)
    unless missing.empty?
      tables = relations.reject { |_k, v| v.fetch(:isView, nil) }.keys.sort
      puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
      return
    end
    unless (cols = relations[fk[1]][:cols]).key?(fk[2]) || (polymorphic_class && cols.key?("#{fk[2]}_id") && cols.key?("#{fk[2]}_type"))
      columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
      puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[2]}. (Columns present in #{fk[1]} are #{columns.join(', ')}.)"
      return
    end
    if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[1] && v[:fk] == fk[2] && v[:inverse_table] == primary_table })
      if is_class && !redundant.last.key?(:class)
        redundant.last[:primary_class] = primary_class # Round out this BT so it can find the proper :source for a HMT association that references an STI subclass
      else
        puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed.  (Already established by #{redundant.first}.)"
      end
      return
    end
  end
  return unless bts # Rails 5.0 and older can have bts end up being nil

  if (assoc_bt = bts[cnstr_name])
    if polymorphic_class
      # Assuming same fk (don't yet support composite keys for polymorphics)
      assoc_bt[:inverse_table] << fk[4]
      assoc_bt[:polymorphic] << polymorphic_class
    else # Expect we could have a composite key going
      if assoc_bt[:fk].is_a?(String)
        assoc_bt[:fk] = [assoc_bt[:fk], fk[2]] unless fk[2] == assoc_bt[:fk]
      elsif assoc_bt[:fk].exclude?(fk[2])
        assoc_bt[:fk] << fk[2]
      end
      assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[2]}"
    end
  else
    inverse_table = [primary_table] if polymorphic_class
    assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
    assoc_bt[:optional] = true if is_optional
    assoc_bt[:polymorphic] = [polymorphic_class] if polymorphic_class
  end
  if is_class
    # For use in finding the proper :source for a HMT association that references an STI subclass
    assoc_bt[:primary_class] = primary_class
    # For use in finding the proper :inverse_of for a BT association that references an STI subclass
    # assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] }
  end

  return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[1] == exclusion[0] && fk[2] == exclusion[1] && primary_table == exclusion[2] } || hms.nil?

  if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
    if assoc_hm[:fk].is_a?(String)
      assoc_hm[:fk] = [assoc_hm[:fk], fk[2]] unless fk[2] == assoc_hm[:fk]
    elsif assoc_hm[:fk].exclude?(fk[2])
      assoc_hm[:fk] << fk[2]
    end
    assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
  else
    inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && fk[0] == ::Brick.apartment_default_tenant
                for_tbl
              else
                fk[1]
              end
    assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: fk_namified.pluralize, alternate_name: bt_assoc_name,
                                      inverse_table: inv_tbl, inverse: assoc_bt }
    assoc_hm[:polymorphic] = true if polymorphic_class
    hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
    this_hm_count = hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
  end
  assoc_bt[:inverse] = assoc_hm
end

._class_pk(dotted_name, multitenant) ⇒ Object



3183
3184
3185
# File 'lib/brick/extensions.rb', line 3183

def _class_pk(dotted_name, multitenant)
  Object.const_get((multitenant ? [dotted_name.split('.').last] : dotted_name.split('.')).map { |nm| "::#{nm.singularize.camelize}" }.join).primary_key
end

._find_csv_separator(stream, likely_separator) ⇒ Object



764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
# File 'lib/brick.rb', line 764

def _find_csv_separator(stream, likely_separator)
  line = nil
  last_char = nil
  in_quotes = false
  ret = +''
  while true
    (line = stream.readline).each_char do |ch|
      likely_separator[ch] += 2 if !in_quotes && last_char == '"' && ch != "\n"
      if ch == '"'
        in_quotes = !in_quotes
        likely_separator[last_char] += 2 if in_quotes && last_char != "\n"
      end
      last_char = ch
    end
    ret << line
    break if !in_quotes || stream.eof?
  end
  likely_separator[line[0]] += 1
  likely_separator[line[-2]] += 1
  ret
end

.additional_references=(ars) ⇒ Object

Additional table associations to use (Think of these as virtual foreign keys perhaps)



431
432
433
434
435
436
437
438
# File 'lib/brick.rb', line 431

def additional_references=(ars)
  if ars
    ars = ars.call if ars.is_a?(Proc)
    ars = ars.to_a unless ars.is_a?(Array)
    ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
    Brick.config.additional_references = ars
  end
end

.always_load_fields=(field_set) ⇒ Object



544
545
546
# File 'lib/brick.rb', line 544

def always_load_fields=(field_set)
  Brick.config.always_load_fields = field_set
end

.apartment_default_tenantObject



150
151
152
# File 'lib/brick.rb', line 150

def apartment_default_tenant
  Apartment.default_tenant || 'public'
end

.apartment_multitenantObject



143
144
145
146
147
148
# File 'lib/brick.rb', line 143

def apartment_multitenant
  if @apartment_multitenant.nil?
    @apartment_multitenant = ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment')
  end
  @apartment_multitenant
end

.api_filterObject



390
391
392
# File 'lib/brick.rb', line 390

def api_filter
  Brick.config.api_filter
end

.api_filter=(proc) ⇒ Object



385
386
387
# File 'lib/brick.rb', line 385

def api_filter=(proc)
  Brick.config.api_filter = proc
end

.api_root=(path) ⇒ Object



370
371
372
# File 'lib/brick.rb', line 370

def api_root=(path)
  Brick.config.api_roots = [path]
end

.api_rootsObject



380
381
382
# File 'lib/brick.rb', line 380

def api_roots
  Brick.config.api_roots
end

.api_roots=(paths) ⇒ Object



375
376
377
# File 'lib/brick.rb', line 375

def api_roots=(paths)
  Brick.config.api_roots = paths
end

.apply_double_underscore_patchObject



726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
# File 'lib/brick.rb', line 726

def apply_double_underscore_patch
  unless @double_underscore_applied
    # Same as normal #camelize and #underscore, just that double-underscores turn into a single underscore
    ActiveSupport::Inflector.class_eval do
      def camelize(term, uppercase_first_letter = true)
        strings = term.to_s.split('__').map do |string|
          # String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent.
          if !uppercase_first_letter || uppercase_first_letter == :lower
            string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match }
          else
            string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match }
          end
          string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
            word = $2
            substituted = inflections.acronyms[word] || word.capitalize! || word
            $1 ? "::#{substituted}" : substituted
          end
          string
        end
        strings.join('_')
      end

      def underscore(camel_cased_word)
        return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)
        regex = inflections.respond_to?(:acronyms_underscore_regex) ? inflections.acronyms_underscore_regex : inflections.acronym_regex
        camel_cased_word.to_s.gsub('::', '/').split('_').map do |word|
          word.gsub!(regex) { "#{$1 && '_' }#{$2.downcase}" }
          word.gsub!(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << '_' }
          word.tr!('-', '_')
          word.downcase!
          word
        end.join('__')
      end
    end
    @double_underscore_applied = true
  end
end

.config {|@config| ... } ⇒ Object Also known as: configure

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns Brick’s global configuration object, a singleton. These settings affect all threads.

Yields:



633
634
635
636
637
# File 'lib/brick.rb', line 633

def config
  @config ||= Brick::Config.instance
  yield @config if block_given?
  @config
end

.controllers_inherit_from=(value) ⇒ Object



410
411
412
# File 'lib/brick.rb', line 410

def controllers_inherit_from=(value)
  Brick.config.controllers_inherit_from = value
end

.ctrl_to_klass(ctrl_path, res_names = {}) ⇒ Object

Attempt to determine an ActiveRecord::Base class and additional STI information when given a controller’s path



687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
# File 'lib/brick.rb', line 687

def ctrl_to_klass(ctrl_path, res_names = {})
  klass = nil
  sti_type = nil

  if res_names.empty?
    ::Brick.relations.each_with_object({}) do |v, s|
      v_parts = v.first.split('.')
      v_parts.shift if v_parts.first == 'public'
      res_names[v_parts.join('.')] = v.first
    end
  end

  c_path_parts = ctrl_path.split('/')
  found = nil
  while c_path_parts.present?
    possible_c_path = c_path_parts.join('.')
    possible_c_path_singular = c_path_parts[0..-2] + [c_path_parts.last.singularize]
    possible_sti = possible_c_path_singular.join('/').camelize
    break if (
               res_name = res_names[found = possible_c_path] ||
                          ((klass = Brick.config.sti_namespace_prefixes.key?("::#{possible_sti}") && possible_sti.constantize) &&
                           (sti_type = possible_sti)) ||
                          # %%% Used to have the more flexible:  (DidYouMean::SpellChecker.new(dictionary: res_names.keys).correct(possible_c_path)).first
                          res_names[found = possible_c_path] || res_names[found = possible_c_path_singular.join('.')] ||
                          ((::Brick.config.table_name_prefixes.key?(tn_prefix = c_path_parts.first) ||
                            ::Brick.config.table_name_prefixes.key?(tn_prefix = "#{c_path_parts.first}_")) &&
                           res_names[found = tn_prefix + c_path_parts.last]
                          )
             ) &&
             (
               klass ||
               ((rel = ::Brick.relations.fetch(res_name, nil)) &&
               (klass ||= rel[:class_name]&.constantize))
             )
    c_path_parts.shift
  end
  [klass, sti_type, found]
end

.custom_columns=(cust_cols) ⇒ Object

Custom columns to add to a table, minimally defined with a name and DSL string.



442
443
444
445
446
447
# File 'lib/brick.rb', line 442

def custom_columns=(cust_cols)
  if cust_cols
    cust_cols = cust_cols.call if cust_cols.is_a?(Proc)
    Brick.config.custom_columns = cust_cols
  end
end

.default_route_fallback=(resource_name) ⇒ Object



536
537
538
# File 'lib/brick.rb', line 536

def default_route_fallback=(resource_name)
  Brick.config.default_route_fallback = resource_name
end

.display_classes(prefix, rels, max_length) ⇒ Object



678
679
680
681
682
683
684
# File 'lib/brick.rb', line 678

def display_classes(prefix, rels, max_length)
  rels.sort.each do |rel|
    ::Brick.auto_models << rel.first
    puts "#{rel.first}#{' ' * (max_length - rel.first.length)}  /#{prefix}#{"#{rel[1]}/" if rel[1]}#{rel.last}"
  end
  puts "\n"
end

.eager_load_classes(do_ar_abstract_bases = false) ⇒ Object



644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# File 'lib/brick.rb', line 644

def eager_load_classes(do_ar_abstract_bases = false)
  ::Brick.is_eager_loading = true
  if ::ActiveSupport.version < ::Gem::Version.new('6') ||
     ::Rails.configuration.instance_variable_get(:@autoloader) == :classic
    if ::ActiveSupport.version < ::Gem::Version.new('4')
      ::Rails.application.eager_load!
    else
      ::Rails.configuration.eager_load_namespaces.select { |ns| ns < ::Rails::Application }.each(&:eager_load!)
    end
  else
    # Same as:  Zeitwerk::Loader.eager_load_all -- plus retry when something skips a beat
    Zeitwerk::Registry.loaders.each { |loader| load_with_retry(loader) }
  end
  abstract_ar_bases = if do_ar_abstract_bases
                        ActiveRecord::Base.descendants.select { |ar| ar.abstract_class? }.map(&:name)
                      end
  ::Brick.is_eager_loading = false
  abstract_ar_bases
end

.enable_apiObject



365
366
367
# File 'lib/brick.rb', line 365

def enable_api
  Brick.config.enable_api
end

.enable_api=(path) ⇒ Object



360
361
362
# File 'lib/brick.rb', line 360

def enable_api=(path)
  Brick.config.enable_api = path
end

.enable_controllers=(value) ⇒ Object

Switches Brick auto-controllers on or off, for all threads



322
323
324
# File 'lib/brick.rb', line 322

def enable_controllers=(value)
  Brick.config.enable_controllers = value
end

.enable_controllers?Boolean

Returns ‘true` if Brick controllers are on, `false` otherwise. This affects all threads. Enabled by default.

Returns:

  • (Boolean)


329
330
331
# File 'lib/brick.rb', line 329

def enable_controllers?
  !!Brick.config.enable_controllers
end

.enable_models=(value) ⇒ Object

Switches Brick auto-models on or off, for all threads



309
310
311
# File 'lib/brick.rb', line 309

def enable_models=(value)
  Brick.config.enable_models = value
end

.enable_models?Boolean

Returns ‘true` if Brick models are on, `false` otherwise. This affects all threads. Enabled by default.

Returns:

  • (Boolean)


316
317
318
# File 'lib/brick.rb', line 316

def enable_models?
  !!Brick.config.enable_models
end

.enable_routes=(value) ⇒ Object

Switches Brick auto-routes on or off, for all threads



348
349
350
# File 'lib/brick.rb', line 348

def enable_routes=(value)
  Brick.config.enable_routes = value
end

.enable_routes?Boolean

Returns ‘true` if Brick routes are on, `false` otherwise. This affects all threads. Enabled by default.

Returns:

  • (Boolean)


355
356
357
# File 'lib/brick.rb', line 355

def enable_routes?
  !!Brick.config.enable_routes
end

.enable_views=(value) ⇒ Object

Switches Brick auto-views on or off, for all threads



335
336
337
# File 'lib/brick.rb', line 335

def enable_views=(value)
  Brick.config.enable_views = value
end

.enable_views?Boolean

Returns ‘true` if Brick views are on, `false` otherwise. This affects all threads. Enabled by default.

Returns:

  • (Boolean)


342
343
344
# File 'lib/brick.rb', line 342

def enable_views?
  !!Brick.config.enable_views
end

.ensure_unique(name, *sources) ⇒ Object



3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
# File 'lib/brick/extensions.rb', line 3091

def ensure_unique(name, *sources)
  base = name
  if (added_num = name.slice!(/_(\d+)$/))
    added_num = added_num[1..-1].to_i
  else
    added_num = 1
  end
  while (
    name = "#{base}_#{added_num += 1}"
    sources.each_with_object(nil) do |v, s|
      s || case v
           when Hash
             v.key?(name)
           when Array
             v.include?(name)
           end
    end
  )
  end
  name
end

.exclude_column(table, col) ⇒ Object



287
288
289
290
# File 'lib/brick.rb', line 287

def exclude_column(table, col)
  puts "Excluding #{table}.#{col}"
  true
end

.exclude_hms=(skips) ⇒ Object

Skip creating a has_many association for these (Uses the same exact three-part format as would define an additional_reference)



457
458
459
460
461
462
463
464
# File 'lib/brick.rb', line 457

def exclude_hms=(skips)
  if skips
    skips = skips.call if skips.is_a?(Proc)
    skips = skips.to_a unless skips.is_a?(Array)
    skips = [skips] unless skips.empty? || skips.first.is_a?(Array)
    Brick.config.exclude_hms = skips
  end
end

.exclude_tables=(value) ⇒ Object



400
401
402
# File 'lib/brick.rb', line 400

def exclude_tables=(value)
  Brick.config.exclude_tables = value
end

.existing_stisObject



105
106
107
# File 'lib/brick.rb', line 105

def existing_stis
  @existing_stis ||= Brick.config.sti_namespace_prefixes.each_with_object({}) { |snp, s| s[snp.first[2..-1]] = snp.last unless snp.first.end_with?('::') }
end

.find_col_renaming(api_ver_path, relation_name) ⇒ Object



3176
3177
3178
3179
3180
3181
# File 'lib/brick/extensions.rb', line 3176

def find_col_renaming(api_ver_path, relation_name)
  ::Brick.config.api_column_renaming&.fetch(
    api_ver_path,
    ::Brick.config.api_column_renaming&.fetch(relation_name, nil)
  )
end

.find_orphans(multi_schema) ⇒ Object

Locate orphaned records



3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
# File 'lib/brick/extensions.rb', line 3114

def find_orphans(multi_schema)
  is_default_schema = multi_schema&.==(::Brick.apartment_default_tenant)
  relations.each_with_object([]) do |v, s|
    frn_tbl = v.first
    next if (relation = v.last).key?(:isView) || config.exclude_tables.include?(frn_tbl) ||
            !(for_pk = (relation[:pkey].values.first&.first))

    is_default_frn_schema = !is_default_schema && multi_schema &&
                            ((frn_parts = frn_tbl.split('.')).length > 1 && frn_parts.first)&.==(::Brick.apartment_default_tenant)
    relation[:fks].select { |_k, assoc| assoc[:is_bt] }.each do |_k, bt|
      begin
        if bt.key?(:polymorphic)
          pri_pk = for_pk
          pri_tables = Brick.config.polymorphics["#{frn_tbl}.#{bt[:fk]}"]
                            .each_with_object(Hash.new { |h, k| h[k] = [] }) do |pri_class, s|
            s[Object.const_get(pri_class).table_name] << pri_class
          end
          fk_id_col = "#{bt[:fk]}_id"
          fk_type_col = "#{bt[:fk]}_type"
          selects = []
          pri_tables.each do |pri_tbl, pri_types|
            # Skip if database is multitenant, we're not focused on "public", and the foreign and primary tables
            # are both in the "public" schema
            next if is_default_frn_schema &&
                    ((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant)

            selects << "SELECT '#{pri_tbl}' AS pri_tbl, frn.#{fk_type_col} AS pri_type, frn.#{fk_id_col} AS pri_id, frn.#{for_pk} AS frn_id
            FROM #{frn_tbl} AS frn
              LEFT OUTER JOIN #{pri_tbl} AS pri ON pri.#{pri_pk} = frn.#{fk_id_col}
            WHERE frn.#{fk_type_col} IN (#{
              pri_types.map { |pri_type| "'#{pri_type}'" }.join(', ')
            }) AND frn.#{bt[:fk]}_id IS NOT NULL AND pri.#{pri_pk} IS NULL\n"
          end
          ActiveRecord::Base.execute_sql(selects.join("UNION ALL\n")).each do |o|
            entry = [frn_tbl, o['frn_id'], o['pri_type'], o['pri_id'], fk_id_col]
            entry << o['pri_tbl'] if (pri_class = Object.const_get(o['pri_type'])) != pri_class.base_class
            s << entry
          end
        else
          # Skip if database is multitenant, we're not focused on "public", and the foreign and primary tables
          # are both in the "public" schema
          pri_tbl = bt.key?(:inverse_table) && bt[:inverse_table]
          next if is_default_frn_schema &&
                  ((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant)

          pri_pk = relations[pri_tbl].fetch(:pkey, nil)&.values&.first&.first ||
                   _class_pk(pri_tbl, multi_schema)
          ActiveRecord::Base.execute_sql(
            "SELECT frn.#{bt[:fk]} AS pri_id, frn.#{for_pk} AS frn_id
            FROM #{frn_tbl} AS frn
              LEFT OUTER JOIN #{pri_tbl} AS pri ON pri.#{pri_pk} = frn.#{bt[:fk]}
            WHERE frn.#{bt[:fk]} IS NOT NULL AND pri.#{pri_pk} IS NULL
            ORDER BY 1, 2"
          ).each { |o| s << [frn_tbl, o['frn_id'], pri_tbl, o['pri_id'], bt[:fk]] }
        end
      rescue StandardError => err
        puts "Strange -- #{err.inspect}"
      end
    end
  end
end

.gem_versionObject

Returns Brick’s ‘::Gem::Version`, convenient for comparisons. This is recommended over `::Brick::VERSION::STRING`.



614
615
616
# File 'lib/brick.rb', line 614

def gem_version
  ::Gem::Version.new(VERSION::STRING)
end

.get_bts_and_hms(model, recalculate = nil) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/brick.rb', line 176

def get_bts_and_hms(model, recalculate = nil)
  if !recalculate && (ret = model.instance_variable_get(:@_brick_bts_and_hms))
    return ret
  end

  model_cols = model.columns_hash
  pk_type = if (mpk = model.primary_key).is_a?(Array)
              # Composite keys should really use:  model.primary_key.map { |pk_part| model_cols[pk_part].type }
              model_cols[mpk.first].type
            else
              mpk && model_cols[mpk].type
            end
  bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
    # %%% The time will come when we will support type checking of composite foreign keys!
    # binding.pry if a.foreign_key.is_a?(Array)
    if a.belongs_to? && !a.polymorphic? && ::Brick.config.polymorphics.fetch(full_assoc_name = "#{model.table_name}.#{a.name}", nil)
      puts "Based on inclusion in ::Brick.polymorphics, marking association #{full_assoc_name} as being polymorphic."
      a.options[:polymorphic] = true
    end
    unless a.polymorphic? || (!a.belongs_to? && (through = a.options[:through])) ||
           (a.klass && ::Brick.config.exclude_tables.exclude?(a.klass.table_name) &&
             (!a.belongs_to? || (same_type = (fk_type = model_cols[a.foreign_key]&.type) == pk_type))
           )
      if same_type == false # We really do want to test specifically for false here, and not nil!
        puts "WARNING:
  Foreign key column #{a.klass.table_name}.#{a.foreign_key} is #{fk_type}, but the primary key it relates to, #{a.active_record.table_name}.#{a.active_record.primary_key}, is #{pk_type}.
  These columns should both be of the same type."
      end
      next
    end

    if a.belongs_to?
      if a.polymorphic?
        rel_poly_bt = relations[model.table_name][:fks].find { |_k, fk| fk[:assoc_name] == a.name.to_s }
        if (primary_tables = rel_poly_bt&.last&.fetch(:inverse_table, [])).is_a?(Array)
          models = rel_poly_bt[1][:polymorphic]&.map { |table| table.singularize.camelize.constantize }
          s.first[a.foreign_key.to_s] = [a.name, models, true]
        else
          # This will come up when using Devise invitable when invited_by_class_name is not
          # specified because in that circumstance it adds a polymorphic :invited_by association,
          # along with appropriate invited_by_type and invited_by_id columns.

          # See if any currently-loaded models have a has_many association over to this polymorphic belongs_to
          hm_models = ActiveRecord::Base.descendants.select do |m|
            m.reflect_on_all_associations.any? { |assoc| !assoc.belongs_to? && assoc.options[:as]&.to_sym == a.name }
          end
          # No need to include subclassed models if their parent is already in the list
          hm_models.reject! { |m| hm_models.any? { |parent| parent != m && m < parent } }
          if hm_models.empty?
            puts "Missing any real indication as to which models \"has_many\" this polymorphic BT in model #{a.active_record.name}:"
            puts "  belongs_to :#{a.name}, polymorphic: true"
          else
            puts "Having analysed all currently-loaded models to infer the various polymorphic has_many associations for #{model.name}, here are the current results:"
            puts "::Brick.polymorphics = { \"#{model.table_name}.#{a.name}\" =>
                     #{hm_models.map(&:name).inspect}
                   }"
            puts 'If you add the above to your brick.rb, it will "cement" these options into place, and avoid this lookup process.'
            s.first[a.foreign_key.to_s] = [a.name, hm_models, true]
          end
        end
      else
        bt_key = a.foreign_key.is_a?(Array) ? a.foreign_key : a.foreign_key.to_s
        s.first[bt_key] = [a.name, a.klass]
      end
    else # This gets all forms of has_many and has_one
      if through # has_many :through or has_one :through
        is_invalid_source = nil
        begin
          if a.through_reflection.macro != :has_many # This HM goes through either a belongs_to or a has_one, so essentially a HOT?
            # Treat it like a belongs_to - just keyed on the association name instead of a foreign_key
            s.first[a.name] = [a.name, a.klass]
            next
          elsif !a.source_reflection # Had considered:  a.active_record.reflect_on_association(a.source_reflection_name).nil?
            is_invalid_source = true
          end
        rescue
          is_invalid_source = true
        end
        if is_invalid_source
          puts "WARNING:  HMT relationship :#{a.name} in model #{model.name} has invalid source :#{a.source_reflection_name}."
          next
        end
      else
        if !a.options.key?(:as) && a.klass.column_names.exclude?(a.foreign_key.to_s)
          options = ", #{a.options.map { |k, v| "#{k.inspect} => #{v.inspect}" }.join(', ')}" if a.options.present?
          puts "WARNING:  Model #{model.name} has this association:
        has_many :#{a.name}#{options}
      which expects column #{a.foreign_key} to exist in table #{a.klass.table_name}.  This column is missing."
          next

        end
      end
      s.last[a.name] = a
    end
  end
  # Mark has_manys that go to an associative ("join") table so that they are skipped in the UI,
  # as well as any possible polymorphic associations
  skip_hms = {}
  hms.each do |hmt|
    if (through = hmt.last.options[:through])
      # ::Brick.relations[hmt.last.through_reflection.table_name]
      skip_hms[through] = nil if hms[through] && model.is_brick? &&
                                 hmt.last.klass != hmt.last.active_record # Don't pull HMs for HMTs that point back to the same table
      # End up with a hash of HMT names pointing to join-table associations
      model._br_associatives[hmt.first] = hms[through] # || hms["#{(opt = hmt.last.options)[:through].to_s.singularize}_#{opt[:source].to_s.pluralize}".to_sym]
    end
  end
  skip_hms.each { |k, _v| hms.delete(k) }
  model.instance_variable_set(:@_brick_bts_and_hms, [bts, hms]) # Cache and return this result
end

.get_status_of_resourcesObject

Identify built out routes, migrations, models, (and also soon controllers and views!) for each resource



3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
# File 'lib/brick/extensions.rb', line 3043

def get_status_of_resources
  rails_root = ::Rails.root.to_s
  migrations = if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{rails_root}/db/migrate")
                 Dir["#{mig_path}/**/*.rb"].each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
                   File.read(v).split("\n").each_with_index do |line, line_idx|
                     # For all non-commented lines, look for any that have "create_table", "alter_table", or "drop_table"
                     if !line.lstrip.start_with?('#') &&
                        (idx = (line.index('create_table ') || line.index('create_table('))&.+(13)) ||
                        (idx = (line.index('alter_table ') || line.index('alter_table('))&.+(12)) ||
                        (idx = (line.index('drop_table ') || line.index('drop_table('))&.+(11))
                       tbl = line[idx..-1].match(/([:'"\w\.]+)/)&.captures&.first
                       if tbl
                         v = v[(rails_root.length)..-1] if v.start_with?(rails_root)
                         v = v[1..-1] if v.start_with?('/')
                         s[tbl.tr(':\'"', '').pluralize] << [v, line_idx + 1]
                       end
                     end
                   end
                 end
               end
  abstract_activerecord_bases = ::Brick.eager_load_classes(true)
  rails_root = ::Rails.root.to_s
  models = ::Brick.relations.each_with_object({}) do |rel, s|
             begin
               if (model = rel.last[:class_name]&.constantize) &&
                  (inh = ActiveRecord::Base._brick_inheriteds[model]&.join(':'))
                 inh = inh[rails_root.length + 1..-1] if inh.start_with?(rails_root)
                 s[rel.first] = [inh, model]
               end
             rescue
             end
           end
  ::Brick.relations.map do |k, v|
    # next if Brick.config.exclude_tables.include?(k)

    tbl_parts = k.split('.')
    tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
    res = tbl_parts.join('.')
    table_name = (model = models[res])&.last&.table_name
    table_name ||= begin
                     v[:class_name].constantize.table_name
                   rescue
                   end
    model = model.first if model.is_a?(Array)
    [k, table_name || k, migrations&.fetch(res, nil), model]
  end
end

.has_ones=(hos) ⇒ Object

Associations to treat as a has_one



474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/brick.rb', line 474

def has_ones=(hos)
  if hos
    hos = hos.call if hos.is_a?(Proc)
    hos = hos.to_a unless hos.is_a?(Array)
    hos = [hos] unless hos.empty? || hos.first.is_a?(Array)
    # Translate to being nested hashes
    Brick.config.has_ones = hos&.each_with_object(Hash.new { |h, k| h[k] = {} }) do |v, s|
      s[v.first][v[1]] = v[2] if v[1]
      s
    end
  end
end

.is_apartment_excluded_table(tbl) ⇒ Object



3187
3188
3189
3190
3191
3192
3193
# File 'lib/brick/extensions.rb', line 3187

def is_apartment_excluded_table(tbl)
  if Object.const_defined?('Apartment')
    tbl_klass = (tnp = ::Brick.config.table_name_prefixes&.find { |k, _v| tbl.start_with?(k) }) ? +"#{tnp.last}::" : +''
    tbl_klass << tbl[tnp&.first&.length || 0..-1].singularize.camelize
    Apartment.excluded_models&.include?(tbl_klass)
  end
end

.json_columns=(cols) ⇒ Object



500
501
502
# File 'lib/brick.rb', line 500

def json_columns=(cols)
  Brick.config.json_columns = cols
end

.license=(key) ⇒ Object



540
541
542
# File 'lib/brick.rb', line 540

def license=(key)
  Brick.config.license = key
end

.load_additional_referencesObject

Load additional references (virtual foreign keys) This is attempted early if a brick initialiser file is found, and then again as a failsafe at the end of our engine’s initialisation %%% Maybe look for differences the second time ‘round and just add new stuff instead of entirely deferring



551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'lib/brick.rb', line 551

def load_additional_references
  return if @_additional_references_loaded || ::Brick.config.mode != :on

  relations = ::Brick.relations
  if (ars = ::Brick.config.additional_references) || ::Brick.config.polymorphics
    is_optional = ActiveRecord.version >= ::Gem::Version.new('5.0')
    if ars
      ars.each do |ar|
        fk = ar.length < 5 ? [nil, +ar[0], ar[1], nil, +ar[2]] : [ar[0], +ar[1], ar[2], ar[3], +ar[4], ar[5]]
        ::Brick._add_bt_and_hm(fk, relations, false, is_optional)
      end
    end
    if (polys = ::Brick.config.polymorphics)
      if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.key?(schema)
        ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
      end
      missing_stis = {}
      polys.each do |k, v|
        table_name, poly = k.split('.')
        v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").each_with_object([]) { |result, s| s << result['typ'] if result['typ'] }
        v.each do |type|
          # Allow polymorphic BT to relate to an STI subclass
          base_type = ::Brick.config.sti_namespace_prefixes["::#{type}"] ||
                      ::Brick.config.sti_namespace_prefixes.find { |k, _v| k.end_with?('::') && type.start_with?(k[2..-1]) }&.last&.[](2..-1)
          if relations.key?(primary_table = (base_type || type).underscore.pluralize)
            ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations,
                                   type, # Polymorphic class
                                   is_optional)
          elsif relations.present?
            missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
          end
        end
      end
      unless missing_stis.empty?
        print "
You might be missing an STI namespace prefix entry for these tables:  #{missing_stis.keys.join(', ')}.
In config/initializers/brick.rb appropriate entries would look something like:
  Brick.sti_namespace_prefixes = {"
        puts missing_stis.map { |_k, missing_sti| "\n    '#{missing_sti}' => 'SomeParentModel'" }.join(',')
        puts "  }
(Just trade out SomeParentModel with some more appropriate one.)"
      end
    end
    @_additional_references_loaded = true
  end

  # Find associative tables that can be set up for has_many :through
  ::Brick.relations.each do |_key, tbl|
    tbl_cols = tbl[:cols].keys
    fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = [fk.last[:assoc_name], fk.last[:inverse_table]] if fk.last[:is_bt]; s }
    # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
    # foreign keys then it can act as an associative table and thus be used with has_many :through.
    if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config. || []) - (tbl[:pkey].values.first || [])).length.zero?
      fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
    end
  end
end

.load_with_retry(loader, autoloaded = nil) ⇒ Object

Some classes (like Phlex::Testing::Rails) will successfully auto-load after a retry



665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/brick.rb', line 665

def load_with_retry(loader, autoloaded = nil)
  autoloaded ||= loader.send(:autoloaded_dirs).dup
  begin
    loader.eager_load
  rescue Zeitwerk::SetupRequired
    # This is fine -- we eager load what can be eager loaded
  rescue Zeitwerk::NameError
    if autoloaded != (new_auto = loader.send(:autoloaded_dirs))
      load_with_retry(loader, new_auto.dup) # Try one more time and it could come together
    end
  end
end

.metadata_columns=(value) ⇒ Object



420
421
422
# File 'lib/brick.rb', line 420

def (value)
  Brick.config. = value
end

.mode=(setting) ⇒ Object



297
298
299
# File 'lib/brick.rb', line 297

def mode=(setting)
  Brick.config.mode = setting
end

.model_descrips=(descrips) ⇒ Object

DSL templates for individual models to provide prettier descriptions of objects



511
512
513
# File 'lib/brick.rb', line 511

def model_descrips=(descrips)
  Brick.config.model_descrips = descrips
end

.models_inherit_from=(value) ⇒ Object



405
406
407
# File 'lib/brick.rb', line 405

def models_inherit_from=(value)
  Brick.config.models_inherit_from = value
end

.namify(name, action = nil) ⇒ Object

Convert spaces to underscores if the second character and onwards is mixed case



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/brick.rb', line 160

def namify(name, action = nil)
  has_uppers = name =~ /[A-Z]+/
  has_lowers = name =~ /[a-z]+/
  name.downcase! if has_uppers && action == :downcase
  if name.include?(' ')
    # All uppers or all lowers?
    if !has_uppers || !has_lowers
      name.titleize.tr(' ', '_')
    else # Mixed uppers and lowers -- just remove existing spaces
      name.tr(' ', '')
    end
  else
    action == :underscore ? name.underscore : name
  end
end

.nested_attributes=(anaf) ⇒ Object

Associations upon which to add #accepts_nested_attributes_for logic



489
490
491
# File 'lib/brick.rb', line 489

def nested_attributes=(anaf)
  Brick.config.nested_attributes = anaf
end

.non_tenanted_modelsObject

If multitenancy is enabled, a list of non-tenanted “global” models



155
156
157
# File 'lib/brick.rb', line 155

def non_tenanted_models
  @pending_models ||= {}
end

.not_nullables=(value) ⇒ Object



425
426
427
# File 'lib/brick.rb', line 425

def not_nullables=(value)
  Brick.config.not_nullables = value
end

.order=(value) ⇒ Object



450
451
452
# File 'lib/brick.rb', line 450

def order=(value)
  Brick.config.order = value
end

.path_prefix=(path) ⇒ Object

Any path prefixing to apply to all auto-generated Brick routes



303
304
305
# File 'lib/brick.rb', line 303

def path_prefix=(path)
  Brick.config.path_prefix = path
end

.polymorphics=(polys) ⇒ Object

Polymorphic associations



494
495
496
497
# File 'lib/brick.rb', line 494

def polymorphics=(polys)
  polys = polys.each_with_object({}) { |poly, s| s[poly] = nil } if polys.is_a?(Array)
  Brick.config.polymorphics = polys || {}
end

.relationsObject

All tables and views (what Postgres calls “relations” including column and foreign key info)



135
136
137
138
139
140
141
# File 'lib/brick.rb', line 135

def relations
  return {} if (ch = ::ActiveRecord::Base.connection_handler).respond_to?(:connection_pool_list) &&
               ch.connection_pool_list.blank?

  # Key our list of relations for this connection off of the connection pool's object_id
  (@relations ||= {})[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } }
end

.schema_behavior=(behavior) ⇒ Object

Database schema to use when analysing existing data, such as deriving a list of polymorphic classes for polymorphics in which it wasn’t originally specified.



524
525
526
# File 'lib/brick.rb', line 524

def schema_behavior=(behavior)
  Brick.config.schema_behavior = (behavior.is_a?(Symbol) ? { behavior => nil } : behavior)
end

.schema_behaviour=(behavior) ⇒ Object

For any Brits out there



528
529
530
# File 'lib/brick.rb', line 528

def schema_behaviour=(behavior)
  Brick.schema_behavior = behavior
end

.serializerObject

Get the Brick serializer used by all threads.



626
627
628
# File 'lib/brick.rb', line 626

def serializer
  Brick.config.serializer
end

.serializer=(value) ⇒ Object

Set the Brick serializer. This setting affects all threads.



620
621
622
# File 'lib/brick.rb', line 620

def serializer=(value)
  Brick.config.serializer = value
end

.set_db_schema(params = nil) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/brick.rb', line 113

def set_db_schema(params = nil)
  # If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into
  # a different tenant.  If so then don't allow schema navigation.
  chosen = if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' &&
              (current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2]
                                                   .split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first) &&
              (is_show_schema_list = (apartment_multitenant && current_schema == ::Brick.default_schema)) &&
              (schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) &&
              ::Brick.db_schemas&.key?(schema)
             Apartment::Tenant.switch!(schema)
             schema
           elsif ::Brick.test_schema
             is_show_schema_list = true
             Apartment::Tenant.switch!(::Brick.test_schema)
             ::Brick.test_schema
           else
             current_schema # Just return the current schema
           end
  [chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list]
end

.sidescroll=(scroll) ⇒ Object



505
506
507
# File 'lib/brick.rb', line 505

def sidescroll=(scroll)
  Brick.config.sidescroll = scroll
end

.skip_database_views=(value) ⇒ Object



395
396
397
# File 'lib/brick.rb', line 395

def skip_database_views=(value)
  Brick.config.skip_database_views = value
end

.skip_index_hms=(value) ⇒ Object

Skip showing counts for these specific has_many associations when building auto-generated #index views



468
469
470
# File 'lib/brick.rb', line 468

def skip_index_hms=(value)
  Brick.config.skip_index_hms = value
end

.sti_modelsObject



101
102
103
# File 'lib/brick.rb', line 101

def sti_models
  @sti_models ||= {}
end

.sti_namespace_prefixes=(snp) ⇒ Object

Module prefixes to build out and associate with specific base STI models



517
518
519
# File 'lib/brick.rb', line 517

def sti_namespace_prefixes=(snp)
  Brick.config.sti_namespace_prefixes = snp
end

.sti_type_column=(type_col) ⇒ Object



532
533
534
# File 'lib/brick.rb', line 532

def sti_type_column=(type_col)
  Brick.config.sti_type_column = (type_col.is_a?(String) ? { type_col => nil } : type_col)
end

.table_name_prefixes=(value) ⇒ Object



415
416
417
# File 'lib/brick.rb', line 415

def table_name_prefixes=(value)
  Brick.config.table_name_prefixes = value
end

.unexclude_column(table, col) ⇒ Object



291
292
293
294
# File 'lib/brick.rb', line 291

def unexclude_column(table, col)
  puts "Unexcluding #{table}.#{col}"
  true
end

.versionObject



640
641
642
# File 'lib/brick.rb', line 640

def version
  VERSION::STRING
end