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
-
.auto_models ⇒ Object
Returns the value of attribute auto_models.
-
.db_schemas ⇒ Object
Returns the value of attribute db_schemas.
-
.default_schema ⇒ Object
Returns the value of attribute default_schema.
-
.is_eager_loading ⇒ Object
Returns the value of attribute is_eager_loading.
-
.is_oracle ⇒ Object
Returns the value of attribute is_oracle.
-
.routes_done ⇒ Object
Returns the value of attribute routes_done.
Class Method Summary collapse
- ._add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false) ⇒ Object
- ._class_pk(dotted_name, multitenant) ⇒ Object
-
.additional_references=(ars) ⇒ Object
Additional table associations to use (Think of these as virtual foreign keys perhaps).
- .apartment_default_tenant ⇒ Object
- .apartment_multitenant ⇒ Object
- .api_root ⇒ Object
- .api_root=(path) ⇒ Object
-
.config {|@config| ... } ⇒ Object
(also: configure)
private
Returns Brick’s global configuration object, a singleton.
-
.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.
-
.custom_columns=(cust_cols) ⇒ Object
Custom columns to add to a table, minimally defined with a name and DSL string.
- .default_route_fallback=(resource_name) ⇒ Object
- .display_classes(prefix, rels, max_length) ⇒ Object
- .eager_load_classes(do_ar_abstract_bases = false) ⇒ Object
- .enable_api ⇒ Object
- .enable_api=(path) ⇒ Object
-
.enable_controllers=(value) ⇒ Object
Switches Brick auto-controllers on or off, for all threads.
-
.enable_controllers? ⇒ Boolean
Returns ‘true` if Brick controllers are on, `false` otherwise.
-
.enable_models=(value) ⇒ Object
Switches Brick auto-models on or off, for all threads.
-
.enable_models? ⇒ Boolean
Returns ‘true` if Brick models are on, `false` otherwise.
-
.enable_routes=(value) ⇒ Object
Switches Brick auto-routes on or off, for all threads.
-
.enable_routes? ⇒ Boolean
Returns ‘true` if Brick routes are on, `false` otherwise.
-
.enable_views=(value) ⇒ Object
Switches Brick auto-views on or off, for all threads.
-
.enable_views? ⇒ Boolean
Returns ‘true` if Brick views are on, `false` otherwise.
- .ensure_unique(name, *sources) ⇒ Object
- .exclude_column(table, col) ⇒ Object
-
.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).
- .exclude_tables=(value) ⇒ Object
- .existing_stis ⇒ Object
-
.find_orphans(multi_schema) ⇒ Object
Locate orphaned records.
-
.gem_version ⇒ Object
Returns Brick’s ‘::Gem::Version`, convenient for comparisons.
- .get_bts_and_hms(model) ⇒ Object
-
.get_status_of_resources ⇒ Object
Identify built out routes, migrations, models, (and also soon controllers and views!) for each resource.
-
.has_ones=(hos) ⇒ Object
Associations to treat as a has_one.
- .license=(key) ⇒ Object
-
.load_additional_references ⇒ Object
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.
- .metadata_columns=(value) ⇒ Object
- .mode=(setting) ⇒ Object
-
.model_descrips=(descrips) ⇒ Object
DSL templates for individual models to provide prettier descriptions of objects.
- .models_inherit_from=(value) ⇒ Object
-
.namify(name, action = nil) ⇒ Object
Convert spaces to underscores if the second character and onwards is mixed case.
-
.non_tenanted_models ⇒ Object
If multitenancy is enabled, a list of non-tenanted “global” models.
- .not_nullables=(value) ⇒ Object
- .order=(value) ⇒ Object
-
.path_prefix=(path) ⇒ Object
Any path prefixing to apply to all auto-generated Brick routes.
-
.polymorphics=(polys) ⇒ Object
Polymorphic associations.
-
.relations ⇒ Object
All tables and views (what Postgres calls “relations” including column and foreign key info).
-
.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.
-
.schema_behaviour=(behavior) ⇒ Object
For any Brits out there.
-
.serializer ⇒ Object
Get the Brick serializer used by all threads.
-
.serializer=(value) ⇒ Object
Set the Brick serializer.
- .set_db_schema(params = nil) ⇒ Object
- .skip_database_views=(value) ⇒ Object
-
.skip_index_hms=(value) ⇒ Object
Skip showing counts for these specific has_many associations when building auto-generated #index views.
- .sti_models ⇒ Object
-
.sti_namespace_prefixes=(snp) ⇒ Object
Module prefixes to build out and associate with specific base STI models.
- .sti_type_column=(type_col) ⇒ Object
- .table_name_prefixes=(value) ⇒ Object
- .unexclude_column(table, col) ⇒ Object
- .version ⇒ Object
Class Attribute Details
.auto_models ⇒ Object
Returns the value of attribute auto_models.
136 137 138 |
# File 'lib/brick.rb', line 136 def auto_models @auto_models end |
.db_schemas ⇒ Object
Returns the value of attribute db_schemas.
136 137 138 |
# File 'lib/brick.rb', line 136 def db_schemas @db_schemas end |
.default_schema ⇒ Object
Returns the value of attribute default_schema.
136 137 138 |
# File 'lib/brick.rb', line 136 def default_schema @default_schema end |
.is_eager_loading ⇒ Object
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_oracle ⇒ Object
Returns the value of attribute is_oracle.
136 137 138 |
# File 'lib/brick.rb', line 136 def is_oracle @is_oracle end |
.routes_done ⇒ Object
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
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 2314 2315 2316 2317 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 2365 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 2266 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] = ::Brick.apartment_default_tenant 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] = ::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]) || (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] == ::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 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
2525 2526 2527 |
# File 'lib/brick/extensions.rb', line 2525 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)
376 377 378 379 380 381 382 383 |
# File 'lib/brick.rb', line 376 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_default_tenant ⇒ Object
167 168 169 |
# File 'lib/brick.rb', line 167 def apartment_default_tenant Apartment.default_tenant || 'public' end |
.apartment_multitenant ⇒ Object
160 161 162 163 164 165 |
# File 'lib/brick.rb', line 160 def apartment_multitenant if @apartment_multitenant.nil? @apartment_multitenant = ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') end @apartment_multitenant end |
.api_root ⇒ Object
340 341 342 |
# File 'lib/brick.rb', line 340 def api_root Brick.config.api_root end |
.api_root=(path) ⇒ Object
335 336 337 |
# File 'lib/brick.rb', line 335 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.
553 554 555 556 557 |
# File 'lib/brick.rb', line 553 def config @config ||= Brick::Config.instance yield @config if block_given? @config 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
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 624 |
# File 'lib/brick.rb', line 592 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('/') 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[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[possible_c_path] || res_names[possible_c_path_singular.join('.')] ) && ( klass || ((rel = ::Brick.relations.fetch(res_name, nil)) && (klass ||= rel[:class_name]&.constantize)) ) c_path_parts.shift end [klass, sti_type] end |
.custom_columns=(cust_cols) ⇒ Object
Custom columns to add to a table, minimally defined with a name and DSL string.
387 388 389 390 391 392 |
# File 'lib/brick.rb', line 387 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
465 466 467 |
# File 'lib/brick.rb', line 465 def default_route_fallback=(resource_name) Brick.config.default_route_fallback = resource_name end |
.display_classes(prefix, rels, max_length) ⇒ Object
583 584 585 586 587 588 589 |
# File 'lib/brick.rb', line 583 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
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 |
# File 'lib/brick.rb', line 564 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 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_api ⇒ Object
330 331 332 |
# File 'lib/brick.rb', line 330 def enable_api Brick.config.enable_api end |
.enable_api=(path) ⇒ Object
325 326 327 |
# File 'lib/brick.rb', line 325 def enable_api=(path) Brick.config.enable_api = path end |
.enable_controllers=(value) ⇒ Object
Switches Brick auto-controllers on or off, for all threads
287 288 289 |
# File 'lib/brick.rb', line 287 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.
294 295 296 |
# File 'lib/brick.rb', line 294 def enable_controllers? !!Brick.config.enable_controllers end |
.enable_models=(value) ⇒ Object
Switches Brick auto-models on or off, for all threads
274 275 276 |
# File 'lib/brick.rb', line 274 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.
281 282 283 |
# File 'lib/brick.rb', line 281 def enable_models? !!Brick.config.enable_models end |
.enable_routes=(value) ⇒ Object
Switches Brick auto-routes on or off, for all threads
313 314 315 |
# File 'lib/brick.rb', line 313 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.
320 321 322 |
# File 'lib/brick.rb', line 320 def enable_routes? !!Brick.config.enable_routes end |
.enable_views=(value) ⇒ Object
Switches Brick auto-views on or off, for all threads
300 301 302 |
# File 'lib/brick.rb', line 300 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.
307 308 309 |
# File 'lib/brick.rb', line 307 def enable_views? !!Brick.config.enable_views end |
.ensure_unique(name, *sources) ⇒ Object
2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 |
# File 'lib/brick/extensions.rb', line 2440 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
252 253 254 255 |
# File 'lib/brick.rb', line 252 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)
402 403 404 405 406 407 408 409 |
# File 'lib/brick.rb', line 402 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
350 351 352 |
# File 'lib/brick.rb', line 350 def exclude_tables=(value) Brick.config.exclude_tables = value end |
.existing_stis ⇒ Object
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
2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 |
# File 'lib/brick/extensions.rb', line 2463 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_version ⇒ Object
Returns Brick’s ‘::Gem::Version`, convenient for comparisons. This is recommended over `::Brick::VERSION::STRING`.
534 535 536 |
# File 'lib/brick.rb', line 534 def gem_version ::Gem::Version.new(VERSION::STRING) end |
.get_bts_and_hms(model) ⇒ Object
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 |
# File 'lib/brick.rb', line 193 def get_bts_and_hms(model) bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s| next unless a.polymorphic? || (!a.belongs_to? && (through = a.[:through])) || (a.klass && ::Brick.config.exclude_tables.exclude?(a.klass.table_name)) 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 = primary_tables&.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. 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.to_s] = [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 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.[:through]) # ::Brick.relations[hmt.last.through_reflection.table_name] skip_hms[through] = nil if hms[through] && model.is_brick? && hmt.last.klass != hmt.last.active_record # Don't pull HMs for HMTs that point back to the same table # End up with a hash of HMT names pointing to join-table associations model._br_associatives[hmt.first] = hms[through] # || hms["#{(opt = hmt.last.options)[:through].to_s.singularize}_#{opt[:source].to_s.pluralize}".to_sym] end end skip_hms.each { |k, _v| hms.delete(k) } [bts, hms] end |
.get_status_of_resources ⇒ Object
Identify built out routes, migrations, models, (and also soon controllers and views!) for each resource
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 |
# File 'lib/brick/extensions.rb', line 2391 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) 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 == ::Brick.apartment_default_tenant 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
419 420 421 422 423 424 425 426 427 428 429 430 |
# File 'lib/brick.rb', line 419 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 |
.license=(key) ⇒ Object
469 470 471 |
# File 'lib/brick.rb', line 469 def license=(key) Brick.config.license = key end |
.load_additional_references ⇒ Object
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
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 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 |
# File 'lib/brick.rb', line 476 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
365 366 367 |
# File 'lib/brick.rb', line 365 def (value) Brick.config. = value end |
.mode=(setting) ⇒ Object
262 263 264 |
# File 'lib/brick.rb', line 262 def mode=(setting) Brick.config.mode = setting end |
.model_descrips=(descrips) ⇒ Object
DSL templates for individual models to provide prettier descriptions of objects
440 441 442 |
# File 'lib/brick.rb', line 440 def model_descrips=(descrips) Brick.config.model_descrips = descrips end |
.models_inherit_from=(value) ⇒ Object
355 356 357 |
# File 'lib/brick.rb', line 355 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
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/brick.rb', line 177 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_models ⇒ Object
If multitenancy is enabled, a list of non-tenanted “global” models
172 173 174 |
# File 'lib/brick.rb', line 172 def non_tenanted_models @pending_models ||= {} end |
.not_nullables=(value) ⇒ Object
370 371 372 |
# File 'lib/brick.rb', line 370 def not_nullables=(value) Brick.config.not_nullables = value end |
.order=(value) ⇒ Object
395 396 397 |
# File 'lib/brick.rb', line 395 def order=(value) Brick.config.order = value end |
.path_prefix=(path) ⇒ Object
Any path prefixing to apply to all auto-generated Brick routes
268 269 270 |
# File 'lib/brick.rb', line 268 def path_prefix=(path) Brick.config.path_prefix = path end |
.polymorphics=(polys) ⇒ Object
Polymorphic associations
433 434 435 436 |
# File 'lib/brick.rb', line 433 def polymorphics=(polys) polys = polys.each_with_object({}) { |poly, s| s[poly] = nil } if polys.is_a?(Array) Brick.config.polymorphics = polys || {} end |
.relations ⇒ Object
All tables and views (what Postgres calls “relations” including column and foreign key info)
155 156 157 158 |
# File 'lib/brick.rb', line 155 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.
453 454 455 |
# File 'lib/brick.rb', line 453 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
457 458 459 |
# File 'lib/brick.rb', line 457 def schema_behaviour=(behavior) Brick.schema_behavior = behavior end |
.serializer ⇒ Object
Get the Brick serializer used by all threads.
546 547 548 |
# File 'lib/brick.rb', line 546 def serializer Brick.config.serializer end |
.serializer=(value) ⇒ Object
Set the Brick serializer. This setting affects all threads.
540 541 542 |
# File 'lib/brick.rb', line 540 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 150 151 152 |
# File 'lib/brick.rb', line 138 def set_db_schema(params = nil) # If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into # a different tenant. If so then don't allow schema navigation. chosen = if (is_show_schema_list = (apartment_multitenant && Apartment::Tenant.current == ::Brick.default_schema)) && (schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) && ::Brick.db_schemas&.key?(schema) Apartment::Tenant.switch!(schema) schema elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' # Just return the current schema current_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',') (current_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first end [chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list] end |
.skip_database_views=(value) ⇒ Object
345 346 347 |
# File 'lib/brick.rb', line 345 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
413 414 415 |
# File 'lib/brick.rb', line 413 def skip_index_hms=(value) Brick.config.skip_index_hms = value end |
.sti_models ⇒ Object
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
446 447 448 |
# File 'lib/brick.rb', line 446 def sti_namespace_prefixes=(snp) Brick.config.sti_namespace_prefixes = snp end |
.sti_type_column=(type_col) ⇒ Object
461 462 463 |
# File 'lib/brick.rb', line 461 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
360 361 362 |
# File 'lib/brick.rb', line 360 def table_name_prefixes=(value) Brick.config.table_name_prefixes = value end |
.unexclude_column(table, col) ⇒ Object
256 257 258 259 |
# File 'lib/brick.rb', line 256 def unexclude_column(table, col) puts "Unexcluding #{table}.#{col}" true end |