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_multitenant ⇒ Object
- .api_root ⇒ Object
- .api_root=(path) ⇒ Object
-
.config {|@config| ... } ⇒ Object
(also: configure)
private
Returns Brick’s global configuration object, a singleton.
-
.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.
-
.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
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_multitenant ⇒ Object
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_root ⇒ Object
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.
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_api ⇒ Object
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.
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.
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.
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.
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_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
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_version ⇒ Object
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.[: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_resources ⇒ Object
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_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
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_models ⇒ Object
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 |
.relations ⇒ Object
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 |
.serializer ⇒ Object
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_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
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 |
.version ⇒ Object
535 536 537 |
# File 'lib/brick.rb', line 535 def version VERSION::STRING end |