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/migration_builder.rb,
lib/generators/brick/migrations_generator.rb,
lib/brick/extensions.rb

Overview

:nodoc:

Defined Under Namespace

Modules: Cucumber, Extensions, MigrationBuilder, Rails, RouteMapper, 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
  return if @_brick_const_missing_done

  @_brick_const_missing_done = true
  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&.length&.> 1 # Classic mode
                    begin
                      base = self
                      unless (base_goal = requested.split('::')[0..-2].join('::')).empty?
                        base = base.parent while base.name != base_goal && base != Object
                      end
                      return base._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 self.const_defined?(schema_name) &&
                                                     (!is_tnp_module || self.const_get(schema_name).is_a?(Class))

               # 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

             # MODULE (overrides from "treat_as_module")
             elsif (::Brick.enable_models? || ::Brick.enable_controllers?) &&
                   (possible_module = (base_module == Object ? '' : "#{base_module.name}::") + class_name) &&
                   ::Brick.config.treat_as_module.include?(possible_module)
               base_module.const_set(class_name.to_sym, (built_module = Module.new))
               [built_module, "module #{possible_module}; end\n"]

             # 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

.established_drfObject

Returns the value of attribute established_drf.



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

def established_drf
  @established_drf
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



3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
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
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
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
# File 'lib/brick/extensions.rb', line 3026

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



3291
3292
3293
# File 'lib/brick/extensions.rb', line 3291

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



780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
# File 'lib/brick.rb', line 780

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)



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

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



560
561
562
# File 'lib/brick.rb', line 560

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

.apartment_default_tenantObject



153
154
155
# File 'lib/brick.rb', line 153

def apartment_default_tenant
  Apartment.default_tenant || 'public'
end

.apartment_multitenantObject



146
147
148
149
150
151
# File 'lib/brick.rb', line 146

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

.api_filterObject



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

def api_filter
  Brick.config.api_filter
end

.api_filter=(proc) ⇒ Object



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

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

.api_root=(path) ⇒ Object



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

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

.api_rootsObject



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

def api_roots
  Brick.config.api_roots
end

.api_roots=(paths) ⇒ Object



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

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

.apply_double_underscore_patchObject



742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
# File 'lib/brick.rb', line 742

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:



649
650
651
652
653
# File 'lib/brick.rb', line 649

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

.controllers_inherit_from=(value) ⇒ Object



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

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



703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
# File 'lib/brick.rb', line 703

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.



452
453
454
455
456
457
# File 'lib/brick.rb', line 452

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



552
553
554
# File 'lib/brick.rb', line 552

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

.display_classes(prefix, rels, max_length) ⇒ Object



694
695
696
697
698
699
700
# File 'lib/brick.rb', line 694

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



660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/brick.rb', line 660

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



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

def enable_api
  Brick.config.enable_api
end

.enable_api=(path) ⇒ Object



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

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

.enable_controllers=(value) ⇒ Object

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



326
327
328
# File 'lib/brick.rb', line 326

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)


333
334
335
# File 'lib/brick.rb', line 333

def enable_controllers?
  !!Brick.config.enable_controllers
end

.enable_models=(value) ⇒ Object

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



313
314
315
# File 'lib/brick.rb', line 313

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)


320
321
322
# File 'lib/brick.rb', line 320

def enable_models?
  !!Brick.config.enable_models
end

.enable_routes=(value) ⇒ Object

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



352
353
354
# File 'lib/brick.rb', line 352

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)


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

def enable_routes?
  !!Brick.config.enable_routes
end

.enable_views=(value) ⇒ Object

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



339
340
341
# File 'lib/brick.rb', line 339

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)


346
347
348
# File 'lib/brick.rb', line 346

def enable_views?
  !!Brick.config.enable_views
end

.ensure_unique(name, *sources) ⇒ Object



3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
# File 'lib/brick/extensions.rb', line 3199

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



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

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)



467
468
469
470
471
472
473
474
# File 'lib/brick.rb', line 467

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



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

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



3284
3285
3286
3287
3288
3289
# File 'lib/brick/extensions.rb', line 3284

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



3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
# File 'lib/brick/extensions.rb', line 3222

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`.



630
631
632
# File 'lib/brick.rb', line 630

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

.get_bts_and_hms(model, recalculate = nil) ⇒ Object



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
286
287
288
289
# File 'lib/brick.rb', line 179

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 models with no table, or subclassed models if their parent is already in the list
          hm_models.reject! { |m| !m.table_exists? || 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
                                 hmt[1].source_reflection.belongs_to? # Don't pull HMs when the HMT is built from HM -> HM
      # 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



3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
# File 'lib/brick/extensions.rb', line 3151

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



484
485
486
487
488
489
490
491
492
493
494
495
# File 'lib/brick.rb', line 484

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

.hmts=(assocs) ⇒ Object

Associations for which to auto-create a has_many _, through: _



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

def hmts=(assocs)
  Brick.config.hmts = assocs
end

.is_apartment_excluded_table(tbl) ⇒ Object



3295
3296
3297
3298
3299
3300
3301
# File 'lib/brick/extensions.rb', line 3295

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



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

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

.license=(key) ⇒ Object



556
557
558
# File 'lib/brick.rb', line 556

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



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
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
# File 'lib/brick.rb', line 567

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



681
682
683
684
685
686
687
688
689
690
691
692
# File 'lib/brick.rb', line 681

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



430
431
432
# File 'lib/brick.rb', line 430

def (value)
  Brick.config. = value
end

.mode=(setting) ⇒ Object



301
302
303
# File 'lib/brick.rb', line 301

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

.model_descrips=(descrips) ⇒ Object

DSL templates for individual models to provide prettier descriptions of objects



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

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

.models_inherit_from=(value) ⇒ Object



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

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/brick.rb', line 163

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



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

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

.non_tenanted_modelsObject

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



158
159
160
# File 'lib/brick.rb', line 158

def non_tenanted_models
  @pending_models ||= {}
end

.not_nullables=(value) ⇒ Object



435
436
437
# File 'lib/brick.rb', line 435

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

.order=(value) ⇒ Object



460
461
462
# File 'lib/brick.rb', line 460

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

.path_prefix=(path) ⇒ Object

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



307
308
309
# File 'lib/brick.rb', line 307

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

.polymorphics=(polys) ⇒ Object

Polymorphic associations



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

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)



138
139
140
141
142
143
144
# File 'lib/brick.rb', line 138

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.



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

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



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

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

.serializerObject

Get the Brick serializer used by all threads.



642
643
644
# File 'lib/brick.rb', line 642

def serializer
  Brick.config.serializer
end

.serializer=(value) ⇒ Object

Set the Brick serializer. This setting affects all threads.



636
637
638
# File 'lib/brick.rb', line 636

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

.set_db_schema(params = nil) ⇒ Object



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

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.
  if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && apartment_multitenant
    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 = current_schema == ::Brick.default_schema
    schema = (is_show_schema_list && params && params['_brick_schema']) || ::Brick.default_schema
    chosen = if is_show_schema_list && ::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
  end
  [chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list]
end

.sidescroll=(scroll) ⇒ Object



521
522
523
# File 'lib/brick.rb', line 521

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

.skip_database_views=(value) ⇒ Object



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

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



478
479
480
# File 'lib/brick.rb', line 478

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



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

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

.sti_type_column=(type_col) ⇒ Object



548
549
550
# File 'lib/brick.rb', line 548

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



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

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

.treat_as_module=(value) ⇒ Object

Causes Decidim to work with this line: Brick.treat_as_module = [‘Decidim::ContentBlocks’]



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

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

.unexclude_column(table, col) ⇒ Object



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

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

.versionObject



656
657
658
# File 'lib/brick.rb', line 656

def version
  VERSION::STRING
end