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/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

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.auto_modelsObject

Returns the value of attribute auto_models.



136
137
138
# File 'lib/brick.rb', line 136

def auto_models
  @auto_models
end

.db_schemasObject

Returns the value of attribute db_schemas.



136
137
138
# File 'lib/brick.rb', line 136

def db_schemas
  @db_schemas
end

.default_schemaObject

Returns the value of attribute default_schema.



136
137
138
# File 'lib/brick.rb', line 136

def default_schema
  @default_schema
end

.is_eager_loadingObject

Returns the value of attribute is_eager_loading.



136
137
138
# File 'lib/brick.rb', line 136

def is_eager_loading
  @is_eager_loading
end

.is_oracleObject

Returns the value of attribute is_oracle.



136
137
138
# File 'lib/brick.rb', line 136

def is_oracle
  @is_oracle
end

.routes_doneObject

Returns the value of attribute routes_done.



136
137
138
# File 'lib/brick.rb', line 136

def routes_done
  @routes_done
end

Class Method Details

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



2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
# File 'lib/brick/extensions.rb', line 2193

def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
  bt_assoc_name = ::Brick.namify(fk[2], :downcase)
  unless is_polymorphic
    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])
  apartment = Object.const_defined?('Apartment') && Apartment
  fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
  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 apartment && apartment&.excluded_models.include?(fk[4].singularize.camelize)
                                    fk[3] = Apartment.default_schema
                                    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]) || (is_polymorphic && 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 is_polymorphic
      # Assuming same fk (don't yet support composite keys for polymorphics)
      assoc_bt[:inverse_table] << fk[4]
    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 is_polymorphic
    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] = true if is_polymorphic
  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] && apartment && fk[0] == Apartment.default_schema
                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 is_polymorphic
    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



2451
2452
2453
# File 'lib/brick/extensions.rb', line 2451

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

.additional_references=(ars) ⇒ Object

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



355
356
357
358
359
360
361
362
# File 'lib/brick.rb', line 355

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

.apartment_multitenantObject



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

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

.api_rootObject



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

def api_root
  Brick.config.api_root
end

.api_root=(path) ⇒ Object



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

def api_root=(path)
  Brick.config.api_root = path
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:



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

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

.custom_columns=(cust_cols) ⇒ Object

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



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

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



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

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

.display_classes(prefix, rels, max_length) ⇒ Object



554
555
556
557
558
559
560
# File 'lib/brick.rb', line 554

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.last}"
  end
  puts "\n"
end

.eager_load_classes(do_ar_abstract_bases = false) ⇒ Object



539
540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/brick.rb', line 539

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
    ::Rails.configuration.eager_load_namespaces.select { |ns| ns < ::Rails::Application }.each(&:eager_load!)
  else
    Zeitwerk::Loader.eager_load_all
  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



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

def enable_api
  Brick.config.enable_api
end

.enable_api=(path) ⇒ Object



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

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

.enable_controllers=(value) ⇒ Object

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



266
267
268
# File 'lib/brick.rb', line 266

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)


273
274
275
# File 'lib/brick.rb', line 273

def enable_controllers?
  !!Brick.config.enable_controllers
end

.enable_models=(value) ⇒ Object

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



253
254
255
# File 'lib/brick.rb', line 253

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)


260
261
262
# File 'lib/brick.rb', line 260

def enable_models?
  !!Brick.config.enable_models
end

.enable_routes=(value) ⇒ Object

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



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

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)


299
300
301
# File 'lib/brick.rb', line 299

def enable_routes?
  !!Brick.config.enable_routes
end

.enable_views=(value) ⇒ Object

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



279
280
281
# File 'lib/brick.rb', line 279

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)


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

def enable_views?
  !!Brick.config.enable_views
end

.ensure_unique(name, *sources) ⇒ Object



2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
# File 'lib/brick/extensions.rb', line 2366

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



231
232
233
234
# File 'lib/brick.rb', line 231

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)



381
382
383
384
385
386
387
388
# File 'lib/brick.rb', line 381

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



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

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

.existing_stisObject



132
133
134
# File 'lib/brick.rb', line 132

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_orphans(multi_schema) ⇒ Object

Locate orphaned records



2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
# File 'lib/brick/extensions.rb', line 2389

def find_orphans(multi_schema)
  is_default_schema = multi_schema&.==(Apartment.default_schema)
  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)&.==(Apartment.default_schema)
    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)&.==(Apartment.default_schema)

            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)&.==(Apartment.default_schema)

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



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

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

.get_bts_and_hms(model) ⇒ Object



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
# File 'lib/brick.rb', line 186

def get_bts_and_hms(model)
  bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
    next if !const_defined?(a.name.to_s.singularize.camelize) && ::Brick.config.exclude_tables.include?(a.plural_name)

    case a.macro
    when :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 = primary_tables&.map { |table| table.singularize.camelize.constantize }
          s.first[a.foreign_key] = [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.
          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"
        end
      else
        s.first[a.foreign_key] = [a.name, a.klass]
      end
    when :has_many, :has_one # This gets has_many as well as has_many :through
      # %%% weed out ones that don't have an available model to reference
      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?
      # 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]
    elsif hmt.last.inverse_of.nil?
      puts "SKIPPING #{hmt.last.name.inspect}"
      # %%% If we don't do this then below associative.name will find that associative is nil
      skip_hms[hmt.last.name] = nil
    end
  end
  skip_hms.each { |k, _v| hms.delete(k) }
  [bts, hms]
end

.get_status_of_resourcesObject

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



2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
# File 'lib/brick/extensions.rb', line 2318

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 do |line|
                     # 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
                         s[tbl.tr(':\'"', '').pluralize] << v
                         break
                       end
                     end
                   end
                 end
               end
  abstract_activerecord_bases = ::Brick.eager_load_classes(true)
  models = if Dir.exist?(model_path = "#{rails_root}/app/models")
             Dir["#{model_path}/**/*.rb"].each_with_object({}) do |v, s|
               File.read(v).split("\n").each do |line|
                 # For all non-commented lines, look for any that start with "class " and also "< ApplicationRecord"
                 if line.lstrip.start_with?('class') && (idx = line.index('class'))
                   model = line[idx + 5..-1].match(/[\s:]+([\w:]+)/)&.captures&.first
                   if model && abstract_activerecord_bases.exclude?(model)
                     klass = begin
                               model.constantize
                             rescue
                             end
                     s[model.underscore.tr('/', '.').pluralize] = [
                       v.start_with?(rails_root) ? v[rails_root.length + 1..-1] : v,
                       klass
                     ]
                   end
                 end
               end
             end
           end
  ::Brick.relations.keys.map do |v|
    tbl_parts = v.split('.')
    tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == Apartment.default_schema
    res = tbl_parts.join('.')
    [v, (model = models[res])&.last&.table_name, migrations&.fetch(res, nil), model&.first]
  end
end

.has_ones=(hos) ⇒ Object

Associations to treat as a has_one



398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/brick.rb', line 398

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

.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



451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/brick.rb', line 451

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|
          if relations.key?(primary_table = type.underscore.pluralize)
            ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true, is_optional)
          else
            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

.metadata_columns=(value) ⇒ Object



344
345
346
# File 'lib/brick.rb', line 344

def (value)
  Brick.config. = value
end

.mode=(setting) ⇒ Object



241
242
243
# File 'lib/brick.rb', line 241

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

.model_descrips=(descrips) ⇒ Object

DSL templates for individual models to provide prettier descriptions of objects



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

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

.models_inherit_from=(value) ⇒ Object



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

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



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/brick.rb', line 170

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

.non_tenanted_modelsObject

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



165
166
167
# File 'lib/brick.rb', line 165

def non_tenanted_models
  @pending_models ||= {}
end

.not_nullables=(value) ⇒ Object



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

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

.order=(value) ⇒ Object



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

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

.path_prefix=(path) ⇒ Object

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



247
248
249
# File 'lib/brick.rb', line 247

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

.polymorphics=(polys) ⇒ Object

Polymorphic associations



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

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)



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

def relations
  # 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.



432
433
434
# File 'lib/brick.rb', line 432

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



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

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

.serializerObject

Get the Brick serializer used by all threads.



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

def serializer
  Brick.config.serializer
end

.serializer=(value) ⇒ Object

Set the Brick serializer. This setting affects all threads.



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

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

.set_db_schema(params = nil) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/brick.rb', line 138

def set_db_schema(params = nil)
  schema = (params ? params['_brick_schema'] : ::Brick.default_schema)
  if schema && ::Brick.db_schemas&.key?(schema)
    ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
    schema
  elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
    # Just return the current schema
    orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
    # ::Brick.apartment_multitenant && tbl_parts.first == Apartment.default_schema
    (orig_schema - ['pg_catalog']).first
  end
end

.skip_database_views=(value) ⇒ Object



324
325
326
# File 'lib/brick.rb', line 324

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



392
393
394
# File 'lib/brick.rb', line 392

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

.sti_modelsObject



128
129
130
# File 'lib/brick.rb', line 128

def sti_models
  @sti_models ||= {}
end

.sti_namespace_prefixes=(snp) ⇒ Object

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



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

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

.sti_type_column=(type_col) ⇒ Object



440
441
442
# File 'lib/brick.rb', line 440

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



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

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

.unexclude_column(table, col) ⇒ Object



235
236
237
238
# File 'lib/brick.rb', line 235

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

.versionObject



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

def version
  VERSION::STRING
end