Module: Brick
- Defined in:
- lib/brick/util.rb,
lib/brick.rb,
lib/brick/config.rb,
lib/brick/join_array.rb,
lib/brick/version_number.rb,
lib/brick/serializers/json.rb,
lib/brick/serializers/yaml.rb,
lib/brick/frameworks/cucumber.rb,
lib/brick/frameworks/rails/engine.rb,
lib/generators/brick/seeds_generator.rb,
lib/generators/brick/models_generator.rb,
lib/generators/brick/install_generator.rb,
lib/generators/brick/migration_builder.rb,
lib/generators/brick/migrations_generator.rb,
lib/brick/extensions.rb
Overview
:nodoc:
Defined Under Namespace
Modules: Cucumber, Extensions, MigrationBuilder, Rails, RouteMapper, RouteSet, Serializers, Util, VERSION Classes: Config, InstallGenerator, JoinArray, JoinHash, MigrationsGenerator, ModelsGenerator, SeedsGenerator
Constant Summary collapse
- ALL_API_ACTIONS =
[:index, :show, :create, :update, :destroy]
- CURRENCY_SYMBOLS =
'$£¢₵€₠ƒ¥₿₩₪₹₫₴₱₲₳₸₺₼₽៛₡₢₣₤₥₦₧₨₭₮₯₰₶₷₻₾'
- ADD_CONST_MISSING =
lambda do return if @_brick_const_missing_done @_brick_const_missing_done = true alias _brick_const_missing const_missing def const_missing(*args) requested = args.first.to_s is_controller = requested.end_with?('Controller') # self.name is nil when a model name is requested in an .erb file if self.name && ::Brick.config.path_prefix split_self_name.shift if (split_self_name = self.name.split('::')).first.blank? # Asking for the prefix module? camelize_prefix = ::Brick.config.path_prefix.camelize if self == Object && requested == camelize_prefix Object.const_set(args.first, (built_module = Module.new)) puts "module #{camelize_prefix}; end\n" return built_module elsif module_parent == Object && self.name == camelize_prefix || module_parent.name == camelize_prefix && module_parent.module_parent == Object split_self_name.shift # Remove the identified path prefix from the split name is_brick_prefix = true if is_controller brick_root = split_self_name.empty? ? self : camelize_prefix.constantize end end end base_module = if self < ActiveRecord::Migration || !self.name brick_root || Object elsif split_self_name&.length&.> 1 # Classic mode begin base = self unless (base_goal = requested.split('::')[0..-2].join('::')).empty? base = base.parent while base.name != base_goal && base != Object end return base._brick_const_missing(*args) rescue NameError # %%% Avoid the error "____ cannot be autoloaded from an anonymous class or module" return self.const_get(args.first) if self.const_defined?(args.first) # unless self == (prnt = (respond_to?(:parent) ? parent : module_parent)) unless self == Object begin return Object._brick_const_missing(*args) rescue NameError return Object.const_get(args.first) if Object.const_defined?(args.first) end end end Object else sti_base = (::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::#{requested}", nil) || ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil))&.constantize self end # puts "#{self.name} - #{args.first}" # Unless it's a Brick prefix looking for a TNP that should create a module ... relations = ::Brick.relations unless (is_tnp_module = (is_brick_prefix && !is_controller && ::Brick.config.table_name_prefixes.values.include?(requested))) # ... first look around for an existing module or class. desired_classname = (self == Object || !name) ? requested : "#{name}::#{requested}" if ((is_defined = self.const_defined?(args.first)) && (possible = self.const_get(args.first)) && # Reset `possible` if it's a controller request that's not a perfect match # Was: (possible = nil) but changed to #local_variable_set in order to suppress the "= should be ==" warning (possible&.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) || # Try to require the respective Ruby file ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) || (self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore)) ) && (require_dependency(filename) || true) && ((possible = self.const_get(args.first)) && possible.name == desired_classname) ) || # If any class has turned up so far (and we're not in the middle of eager loading) # then return what we've found. (is_defined && !::Brick.is_eager_loading) # Used to also have: && possible != self if ((!brick_root && (filename || possible.instance_of?(Class))) || (possible.instance_of?(Module) && possible&.module_parent == self) || (possible.instance_of?(Class) && possible == self)) && # Are we simply searching for ourselves? # Skip when what we found as `possible` is not related to the base class of an STI model (!sti_base || possible.is_a?(sti_base)) # if possible.is_a?(ActiveRecord::Base) && !possible.abstract_class? && (pk = possible.primary_key) && # !(relation = relations.fetch(possible.table_name, nil))&.fetch(:pks, nil) # binding.pry # x = 5 # end return possible end end end class_name = ::Brick.namify(requested) # CONTROLLER result = if ::Brick.enable_controllers? && is_controller && (plural_class_name = class_name[0..-11]).length.positive? # Otherwise now it's up to us to fill in the gaps controller_class_name = +'' full_class_name = +'' unless self == Object controller_class_name << ((split_self_name&.first && split_self_name.join('::')) || self.name) full_class_name << "::#{controller_class_name}" controller_class_name << '::' end # (Go over to underscores for a moment so that if we have something come in like VABCsController then the model name ends up as # Vabc instead of VABC) singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize full_class_name << "::#{singular_class_name}" skip_controller = nil if plural_class_name == 'BrickOpenapi' || ( (::Brick.config.add_status || ::Brick.config.add_orphans) && plural_class_name == 'BrickGem' ) || begin model = self.const_get(full_class_name) # In the very rare case that we've picked up a MODULE which has the same name as what would be the # resource's MODEL name, just build out an appropriate auto-model on-the-fly. (RailsDevs code has this in PayCustomer.) # %%% We don't yet display the code for this new model if model && !model.is_a?(Class) model, _code = Object.send(:build_model, relations, model.module_parent, model.module_parent.name, singular_class_name) end rescue NameError # If the const_get for the model has failed... skip_controller = true # ... then just fall through and allow it to fail when trying to loading the ____Controller class normally. end end unless skip_controller Object.send(:build_controller, self, class_name, plural_class_name, model, relations) end # MODULE elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match? # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing (base_module == Object || (camelize_prefix && base_module == Object.const_get(camelize_prefix))) && (schema_name = [(singular_table_name = class_name.underscore), (table_name = singular_table_name.pluralize), ::Brick.is_oracle ? class_name.upcase : class_name, (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas&.include?(s) }&.camelize || (::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name) || (::Brick.config.table_name_prefixes&.values&.include?(class_name) && class_name)) return self.const_get(schema_name) if self.const_defined?(schema_name) && (!is_tnp_module || self.const_get(schema_name).is_a?(Class)) # Build out a module for the schema if it's namespaced # schema_name = schema_name.camelize base_module.const_set(schema_name.to_sym, (built_module = Module.new)) [built_module, "module #{schema_name}; end\n"] # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied # MODULE (overrides from "treat_as_module") elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && (possible_module = (base_module == Object ? '' : "#{base_module.name}::") + class_name) && ::Brick.config.treat_as_module.include?(possible_module) base_module.const_set(class_name.to_sym, (built_module = Module.new)) [built_module, "module #{possible_module}; end\n"] # AVO Resource elsif base_module == Object && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && requested.end_with?('Resource') && # Expect that anything called MotorResource or SpinaResource could be from those administrative gems requested.length > 8 && ['MotorResource', 'SpinaResource'].exclude?(requested) if (model = Object.const_get(requested[0..-9])) && model < ActiveRecord::Base require 'generators/avo/resource_generator' field_generator = Generators::Avo::ResourceGenerator.new(['']) field_generator.instance_variable_set(:@model, model) fields = field_generator.send(:generate_fields)&.split("\n") &.each_with_object([]) do |f, s| if (f = f.strip).start_with?('field ') f = f[6..-1].split(',') s << [f.first[1..-1].to_sym, [f[1][1..-1].split(': :').map(&:to_sym)].to_h] end end || [] built_resource = Class.new(Avo::BaseResource) do |new_resource_class| self.model_class = model self.title = :brick_descrip self.includes = [] if (!model.is_view? && mod_pk = model.primary_key) field((mod_pk.is_a?(Array) ? mod_pk.first : mod_pk).to_sym, { as: :id }) end # Create a call such as: field :name, as: :text fields.each do |f| # Add proper types if this is a polymorphic belongs_to if f.last == { as: :belongs_to } && (fk = ::Brick.relations[model.table_name][:fks].find { |k, v| v[:assoc_name] == f.first.to_s }) && fk.last.fetch(:polymorphic, nil) poly_types = fk.last.fetch(:inverse_table, nil)&.each_with_object([]) do |poly_table, s| s << Object.const_get(::Brick.relations[poly_table][:class_name]) end if poly_types.present? f.last[:polymorphic_as] = f.first f.last[:types] = poly_types end end self.send(:field, *f) end end Object.const_set(requested.to_sym, built_resource) [built_resource, nil] end # MODEL elsif ::Brick.enable_models? # Custom inheritable Brick base model? class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick') Object.send(:build_model, relations, base_module, name, class_name, inheritable_name) end if result built_class, code = result puts "\n#{code}\n" built_class elsif !schema_name && ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}") # module_prefixes = type_name.split('::') # path = base_module.name.split('::')[0..-2] + [] # module_prefixes.unshift('') unless module_prefixes.first.blank? # candidate_file = ::Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb') base_module._brick_const_missing(*args) # elsif base_module != Object # module_parent.const_missing(*args) elsif Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ... (Rails::Autoloaders.respond_to?(:zeitwerk_enabled?) ? Rails::Autoloaders.zeitwerk_enabled? : true) self._brick_const_missing(*args) # ... rely solely on Zeitwerk. else # Classic mode unless (found = base_module._brick_const_missing(*args)) puts "MISSING! #{base_module.name} #{args.inspect} #{table_name}" end found end end # Support Rails < 6.0 which adds #parent instead of #module_parent unless respond_to?(:module_parent) def module_parent # Weirdly for Grape::API does NOT come in with the proper class, but some anonymous Class thing parent end end end
Class Attribute Summary collapse
-
.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.
-
.established_drf ⇒ Object
Returns the value of attribute established_drf.
-
.initializer_loaded ⇒ Object
Returns the value of attribute initializer_loaded.
-
.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.
-
.test_schema ⇒ Object
Returns the value of attribute test_schema.
Class Method Summary collapse
- ._add_bt_and_hm(fk, relations, polymorphic_class = nil, is_optional = false) ⇒ Object
- ._class_pk(dotted_name, multitenant) ⇒ Object
- ._find_csv_separator(stream, likely_separator) ⇒ Object
-
.additional_references=(ars) ⇒ Object
Additional table associations to use (Think of these as virtual foreign keys perhaps).
- .always_load_fields=(field_set) ⇒ Object
- .apartment_default_tenant ⇒ Object
- .apartment_multitenant ⇒ Object
- .api_filter ⇒ Object
- .api_filter=(proc) ⇒ Object
- .api_root=(path) ⇒ Object
- .api_roots ⇒ Object
- .api_roots=(paths) ⇒ Object
- .apply_double_underscore_patch ⇒ Object
- .ar_base ⇒ Object
-
.config {|@config| ... } ⇒ Object
(also: configure)
private
Returns Brick’s global configuration object, a singleton.
- .controllers_inherit_from=(value) ⇒ Object
-
.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_col_renaming(api_ver_path, relation_name) ⇒ 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, recalculate = nil) ⇒ Object
- .get_possible_schemas ⇒ 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.
-
.hmts=(assocs) ⇒ Object
Associations for which to auto-create a has_many _, through: _.
- .is_apartment_excluded_table(tbl) ⇒ Object
- .is_geography?(val) ⇒ Boolean
- .json_columns=(cols) ⇒ Object
- .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.
-
.load_with_retry(loader, autoloaded = nil) ⇒ Object
Some classes (like Phlex::Testing::Rails) will successfully auto-load after a retry.
- .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.
-
.nested_attributes=(anaf) ⇒ Object
Associations upon which to add #accepts_nested_attributes_for logic.
-
.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
- .sidescroll=(scroll) ⇒ 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
-
.treat_as_module=(value) ⇒ Object
Causes Decidim to work with this line: Brick.treat_as_module = [‘Decidim::ContentBlocks’].
- .unexclude_column(table, col) ⇒ Object
- .version ⇒ Object
Class Attribute Details
.auto_models ⇒ Object
Returns the value of attribute auto_models.
109 110 111 |
# File 'lib/brick.rb', line 109 def auto_models @auto_models end |
.db_schemas ⇒ Object
Returns the value of attribute db_schemas.
109 110 111 |
# File 'lib/brick.rb', line 109 def db_schemas @db_schemas end |
.default_schema ⇒ Object
Returns the value of attribute default_schema.
109 110 111 |
# File 'lib/brick.rb', line 109 def default_schema @default_schema end |
.established_drf ⇒ Object
Returns the value of attribute established_drf.
109 110 111 |
# File 'lib/brick.rb', line 109 def established_drf @established_drf end |
.initializer_loaded ⇒ Object
Returns the value of attribute initializer_loaded.
109 110 111 |
# File 'lib/brick.rb', line 109 def initializer_loaded @initializer_loaded end |
.is_eager_loading ⇒ Object
Returns the value of attribute is_eager_loading.
109 110 111 |
# File 'lib/brick.rb', line 109 def is_eager_loading @is_eager_loading end |
.is_oracle ⇒ Object
Returns the value of attribute is_oracle.
109 110 111 |
# File 'lib/brick.rb', line 109 def is_oracle @is_oracle end |
.routes_done ⇒ Object
Returns the value of attribute routes_done.
109 110 111 |
# File 'lib/brick.rb', line 109 def routes_done @routes_done end |
.test_schema ⇒ Object
Returns the value of attribute test_schema.
109 110 111 |
# File 'lib/brick.rb', line 109 def test_schema @test_schema end |
Class Method Details
._add_bt_and_hm(fk, relations, polymorphic_class = nil, is_optional = false) ⇒ Object
3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 |
# File 'lib/brick/extensions.rb', line 3065 def _add_bt_and_hm(fk, relations, polymorphic_class = nil, is_optional = false) bt_assoc_name = ::Brick.namify(fk[2].dup, :downcase) unless polymorphic_class bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id') bt_assoc_name[-3] == '_' ? bt_assoc_name[0..-4] : bt_assoc_name[0..-3] elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_') bt_assoc_name[0..-3] # Make the bold assumption that we can just peel off any final ID part else "#{bt_assoc_name}_bt" end end bt_assoc_name = "#{bt_assoc_name}_" if bt_assoc_name == 'attribute' # %%% Temporary schema patch for_tbl = fk[1] fk_namified = ::Brick.namify(fk[1]) fk[0] = ::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(fk_namified) fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} } # %%% Do we miss out on has_many :through or even HM based on constantizing this model early? # Maybe it's already gotten this info because we got as far as to say there was a unique class primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class)) pri_tbl = (primary_class = fk[4][:class].constantize).table_name if (pri_tbl_parts = pri_tbl.split('.')).length > 1 fk[3] = pri_tbl_parts.first end else is_schema = if ::Brick.config.schema_behavior[:multitenant] # If Apartment gem lists the primary table as being associated with a non-tenanted model # then use 'public' schema for the primary table if ::Brick.is_apartment_excluded_table(fk[4]) fk[3] = ::Brick.apartment_default_tenant true end else fk[3] && fk[3] != ::Brick.default_schema && fk[3] != 'public' end pri_tbl = fk[4] is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl end hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class unless (cnstr_name = fk[5]) # For any appended references (those that come from config), arrive upon a definitely unique constraint name pri_tbl = is_class ? fk[4][:class].underscore : pri_tbl pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl&.singularize != bt_assoc_name cnstr_name = ensure_unique(+"(brick) #{for_tbl}_#{pri_tbl}", bts, hms) missing = [] missing << fk[1] unless relations.key?(fk[1]) missing << primary_table unless is_class || relations.key?(primary_table) unless missing.empty? tables = relations.reject { |_k, v| v.is_a?(Hash) && v.fetch(:isView, nil) }.keys .select { |table_name| table_name.is_a?(String) }.sort puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)" return end unless (cols = relations[fk[1]][:cols]).key?(fk[2]) || (polymorphic_class && cols.key?("#{fk[2]}_id") && cols.key?("#{fk[2]}_type")) columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" } puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[2]}. (Columns present in #{fk[1]} are #{columns.join(', ')}.)" return end if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[1] && v[:fk] == fk[2] && v[:inverse_table] == primary_table }) if is_class && !redundant.last.key?(:class) redundant.last[:primary_class] = primary_class # Round out this BT so it can find the proper :source for a HMT association that references an STI subclass else puts "Brick: Additional reference #{fk.inspect} is redundant and can be removed. (Already established by #{redundant.first}.)" end return end end return unless bts # Rails 5.0 and older can have bts end up being nil if (assoc_bt = bts[cnstr_name]) if polymorphic_class # Assuming same fk (don't yet support composite keys for polymorphics) assoc_bt[:inverse_table] << fk[4] assoc_bt[:polymorphic] << polymorphic_class else # Expect we could have a composite key going if assoc_bt[:fk].is_a?(String) assoc_bt[:fk] = [assoc_bt[:fk], fk[2]] unless fk[2] == assoc_bt[:fk] elsif assoc_bt[:fk].exclude?(fk[2]) assoc_bt[:fk] << fk[2] end assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[2]}" end else inverse_table = [primary_table] if polymorphic_class assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table } assoc_bt[:optional] = true if is_optional assoc_bt[:polymorphic] = [polymorphic_class] if polymorphic_class end if is_class # For use in finding the proper :source for a HMT association that references an STI subclass assoc_bt[:primary_class] = primary_class # For use in finding the proper :inverse_of for a BT association that references an STI subclass # assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] } end return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[1] == exclusion[0] && fk[2] == exclusion[1] && primary_table == exclusion[2] } || hms.nil? if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil)) if assoc_hm[:fk].is_a?(String) assoc_hm[:fk] = [assoc_hm[:fk], fk[2]] unless fk[2] == assoc_hm[:fk] elsif assoc_hm[:fk].exclude?(fk[2]) assoc_hm[:fk] << fk[2] end assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name else inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && fk[0] == ::Brick.apartment_default_tenant for_tbl else fk[1] end assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: fk_namified.pluralize, alternate_name: bt_assoc_name, inverse_table: inv_tbl, inverse: assoc_bt } assoc_hm[:polymorphic] = true if polymorphic_class hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} } this_hm_count = hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1 end assoc_bt[:inverse] = assoc_hm end |
._class_pk(dotted_name, multitenant) ⇒ Object
3339 3340 3341 |
# File 'lib/brick/extensions.rb', line 3339 def _class_pk(dotted_name, multitenant) Object.const_get((multitenant ? [dotted_name.split('.').last] : dotted_name.split('.')).map { |nm| "::#{nm.singularize.camelize}" }.join).primary_key end |
._find_csv_separator(stream, likely_separator) ⇒ Object
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 |
# File 'lib/brick.rb', line 809 def _find_csv_separator(stream, likely_separator) line = nil last_char = nil in_quotes = false ret = +'' while true (line = stream.readline).each_char do |ch| likely_separator[ch] += 2 if !in_quotes && last_char == '"' && ch != "\n" if ch == '"' in_quotes = !in_quotes likely_separator[last_char] += 2 if in_quotes && last_char != "\n" end last_char = ch end ret << line break if !in_quotes || stream.eof? end likely_separator[line[0]] += 1 likely_separator[line[-2]] += 1 ret end |
.additional_references=(ars) ⇒ Object
Additional table associations to use (Think of these as virtual foreign keys perhaps)
466 467 468 469 470 471 472 473 |
# File 'lib/brick.rb', line 466 def additional_references=(ars) if ars ars = ars.call if ars.is_a?(Proc) ars = ars.to_a unless ars.is_a?(Array) ars = [ars] unless ars.empty? || ars.first.is_a?(Array) Brick.config.additional_references = ars end end |
.always_load_fields=(field_set) ⇒ Object
585 586 587 |
# File 'lib/brick.rb', line 585 def always_load_fields=(field_set) Brick.config.always_load_fields = field_set end |
.apartment_default_tenant ⇒ Object
174 175 176 |
# File 'lib/brick.rb', line 174 def apartment_default_tenant Apartment.default_tenant || 'public' end |
.apartment_multitenant ⇒ Object
167 168 169 170 171 172 |
# File 'lib/brick.rb', line 167 def apartment_multitenant if @apartment_multitenant.nil? @apartment_multitenant = ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') end @apartment_multitenant end |
.api_filter ⇒ Object
419 420 421 |
# File 'lib/brick.rb', line 419 def api_filter Brick.config.api_filter end |
.api_filter=(proc) ⇒ Object
414 415 416 |
# File 'lib/brick.rb', line 414 def api_filter=(proc) Brick.config.api_filter = proc end |
.api_root=(path) ⇒ Object
399 400 401 |
# File 'lib/brick.rb', line 399 def api_root=(path) Brick.config.api_roots = [path] end |
.api_roots ⇒ Object
409 410 411 |
# File 'lib/brick.rb', line 409 def api_roots Brick.config.api_roots end |
.api_roots=(paths) ⇒ Object
404 405 406 |
# File 'lib/brick.rb', line 404 def api_roots=(paths) Brick.config.api_roots = paths end |
.apply_double_underscore_patch ⇒ Object
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 |
# File 'lib/brick.rb', line 771 def apply_double_underscore_patch unless @double_underscore_applied # Same as normal #camelize and #underscore, just that double-underscores turn into a single underscore ActiveSupport::Inflector.class_eval do def camelize(term, uppercase_first_letter = true) strings = term.to_s.split('__').map do |string| # String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent. if !uppercase_first_letter || uppercase_first_letter == :lower string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match } else string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match } end string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do word = $2 substituted = inflections.acronyms[word] || word.capitalize! || word $1 ? "::#{substituted}" : substituted end string end strings.join('_') end def underscore(camel_cased_word) return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word) regex = inflections.respond_to?(:acronyms_underscore_regex) ? inflections.acronyms_underscore_regex : inflections.acronym_regex camel_cased_word.to_s.gsub('::', '/').split('_').map do |word| word.gsub!(regex) { "#{$1 && '_' }#{$2.downcase}" } word.gsub!(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << '_' } word.tr!('-', '_') word.downcase! word end.join('__') end end @double_underscore_applied = true end end |
.ar_base ⇒ Object
3188 3189 3190 |
# File 'lib/brick/extensions.rb', line 3188 def ar_base @ar_base ||= Object.const_defined?(:ApplicationRecord) ? ApplicationRecord : Class.new(ActiveRecord::Base) 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.
676 677 678 679 680 |
# File 'lib/brick.rb', line 676 def config @config ||= Brick::Config.instance yield @config if block_given? @config end |
.controllers_inherit_from=(value) ⇒ Object
439 440 441 |
# File 'lib/brick.rb', line 439 def controllers_inherit_from=(value) Brick.config.controllers_inherit_from = value end |
.ctrl_to_klass(ctrl_path, res_names = {}) ⇒ Object
Attempt to determine an ActiveRecord::Base class and additional STI information when given a controller’s path
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 |
# File 'lib/brick.rb', line 730 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| next if v.first.is_a?(Symbol) v_parts = v.first.split('.') v_parts.shift if v_parts.first == 'public' res_names[v_parts.join('.')] = v.first end end c_path_parts = ctrl_path.split('/') found = nil while c_path_parts.present? possible_c_path = c_path_parts.join('.') possible_c_path_singular = c_path_parts[0..-2] + [c_path_parts.last.singularize] possible_sti = possible_c_path_singular.join('/').camelize break if ( res_name = res_names[found = possible_c_path] || ((klass = Brick.config.sti_namespace_prefixes.key?("::#{possible_sti}") && possible_sti.constantize) && (sti_type = possible_sti)) || # %%% Used to have the more flexible: (DidYouMean::SpellChecker.new(dictionary: res_names.keys).correct(possible_c_path)).first res_names[found = possible_c_path] || res_names[found = possible_c_path_singular.join('.')] || ((::Brick.config.table_name_prefixes.key?(tn_prefix = c_path_parts.first) || ::Brick.config.table_name_prefixes.key?(tn_prefix = "#{c_path_parts.first}_")) && res_names[found = tn_prefix + c_path_parts.last] ) ) && ( klass || ((rel = ::Brick.relations.fetch(res_name, nil)) && (klass ||= rel[:class_name]&.constantize)) ) c_path_parts.shift end [klass, sti_type, found] end |
.custom_columns=(cust_cols) ⇒ Object
Custom columns to add to a table, minimally defined with a name and DSL string.
477 478 479 480 481 482 |
# File 'lib/brick.rb', line 477 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
577 578 579 |
# File 'lib/brick.rb', line 577 def default_route_fallback=(resource_name) Brick.config.default_route_fallback = resource_name end |
.display_classes(prefix, rels, max_length) ⇒ Object
721 722 723 724 725 726 727 |
# File 'lib/brick.rb', line 721 def display_classes(prefix, rels, max_length) rels.sort.each do |rel| ::Brick.auto_models << rel.first puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{prefix}#{"#{rel[1]}/" if rel[1]}#{rel.last}" end puts "\n" end |
.eager_load_classes(do_ar_abstract_bases = false) ⇒ Object
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 |
# File 'lib/brick.rb', line 687 def eager_load_classes(do_ar_abstract_bases = false) ::Brick.is_eager_loading = true if ::ActiveSupport.version < ::Gem::Version.new('6') || ::Rails.configuration.instance_variable_get(:@autoloader) == :classic if ::ActiveSupport.version < ::Gem::Version.new('4') ::Rails.application.eager_load! else ::Rails.configuration.eager_load_namespaces.select { |ns| ns < ::Rails::Application }.each(&:eager_load!) end else # Same as: Zeitwerk::Loader.eager_load_all -- plus retry when something skips a beat Zeitwerk::Registry.loaders.each { |loader| load_with_retry(loader) } end abstract_ar_bases = if do_ar_abstract_bases ActiveRecord::Base.descendants.select { |ar| ar.abstract_class? }.map(&:name) end ::Brick.is_eager_loading = false abstract_ar_bases end |
.enable_api ⇒ Object
394 395 396 |
# File 'lib/brick.rb', line 394 def enable_api Brick.config.enable_api end |
.enable_api=(path) ⇒ Object
389 390 391 |
# File 'lib/brick.rb', line 389 def enable_api=(path) Brick.config.enable_api = path end |
.enable_controllers=(value) ⇒ Object
Switches Brick auto-controllers on or off, for all threads
351 352 353 |
# File 'lib/brick.rb', line 351 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.
358 359 360 |
# File 'lib/brick.rb', line 358 def enable_controllers? !!Brick.config.enable_controllers end |
.enable_models=(value) ⇒ Object
Switches Brick auto-models on or off, for all threads
338 339 340 |
# File 'lib/brick.rb', line 338 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.
345 346 347 |
# File 'lib/brick.rb', line 345 def enable_models? !!Brick.config.enable_models end |
.enable_routes=(value) ⇒ Object
Switches Brick auto-routes on or off, for all threads
377 378 379 |
# File 'lib/brick.rb', line 377 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.
384 385 386 |
# File 'lib/brick.rb', line 384 def enable_routes? !!Brick.config.enable_routes end |
.enable_views=(value) ⇒ Object
Switches Brick auto-views on or off, for all threads
364 365 366 |
# File 'lib/brick.rb', line 364 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.
371 372 373 |
# File 'lib/brick.rb', line 371 def enable_views? !!Brick.config.enable_views end |
.ensure_unique(name, *sources) ⇒ Object
3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 |
# File 'lib/brick/extensions.rb', line 3246 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
312 313 314 315 |
# File 'lib/brick.rb', line 312 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)
492 493 494 495 496 497 498 499 |
# File 'lib/brick.rb', line 492 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
429 430 431 |
# File 'lib/brick.rb', line 429 def exclude_tables=(value) Brick.config.exclude_tables = value end |
.existing_stis ⇒ Object
105 106 107 |
# File 'lib/brick.rb', line 105 def existing_stis @existing_stis ||= Brick.config.sti_namespace_prefixes.each_with_object({}) { |snp, s| s[snp.first[2..-1]] = snp.last unless snp.first.end_with?('::') } end |
.find_col_renaming(api_ver_path, relation_name) ⇒ Object
3332 3333 3334 3335 3336 3337 |
# File 'lib/brick/extensions.rb', line 3332 def find_col_renaming(api_ver_path, relation_name) ::Brick.config.api_column_renaming&.fetch( api_ver_path, ::Brick.config.api_column_renaming&.fetch(relation_name, nil) ) end |
.find_orphans(multi_schema) ⇒ Object
Locate orphaned records
3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 |
# File 'lib/brick/extensions.rb', line 3269 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 frn_tbl.is_a?(Symbol) || # Skip internal metadata entries (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`.
657 658 659 |
# File 'lib/brick.rb', line 657 def gem_version ::Gem::Version.new(VERSION::STRING) end |
.get_bts_and_hms(model, recalculate = nil) ⇒ Object
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/brick.rb', line 200 def get_bts_and_hms(model, recalculate = nil) if !recalculate && (ret = model.instance_variable_get(:@_brick_bts_and_hms)) return ret end model_cols = model.columns_hash pk_type = if (mpk = model.primary_key).is_a?(Array) # Composite keys should really use: model.primary_key.map { |pk_part| model_cols[pk_part].type } model_cols[mpk.first].type else mpk && model_cols[mpk].type end bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s| # %%% The time will come when we will support type checking of composite foreign keys! # binding.pry if a.foreign_key.is_a?(Array) if a.belongs_to? && !a.polymorphic? && ::Brick.config.polymorphics.fetch(full_assoc_name = "#{model.table_name}.#{a.name}", nil) puts "Based on inclusion in ::Brick.polymorphics, marking association #{full_assoc_name} as being polymorphic." a.[:polymorphic] = true end unless a.polymorphic? || (!a.belongs_to? && (through = a.[:through])) || (a.klass && ::Brick.config.exclude_tables.exclude?(a.klass.table_name) && (!a.belongs_to? || (same_type = (fk_type = model_cols[a.foreign_key]&.type) == pk_type)) ) if same_type == false # We really do want to test specifically for false here, and not nil! puts "WARNING: Foreign key column #{a.klass.table_name}.#{a.foreign_key} is #{fk_type}, but the primary key it relates to, #{a.active_record.table_name}.#{a.active_record.primary_key}, is #{pk_type}. These columns should both be of the same type." end next end if a.belongs_to? if a.polymorphic? rel_poly_bt = relations[model.table_name][:fks].find { |_k, fk| fk[:assoc_name] == a.name.to_s } if (primary_tables = rel_poly_bt&.last&.fetch(:inverse_table, [])).is_a?(Array) models = rel_poly_bt[1][:polymorphic]&.map { |table| table.singularize.camelize.constantize } s.first[a.foreign_key.to_s] = [a.name, models, true] else # This will come up when using Devise invitable when invited_by_class_name is not # specified because in that circumstance it adds a polymorphic :invited_by association, # along with appropriate invited_by_type and invited_by_id columns. # See if any currently-loaded models have a has_many association over to this polymorphic belongs_to hm_models = ActiveRecord::Base.descendants.select do |m| m.reflect_on_all_associations.any? { |assoc| !assoc.belongs_to? && assoc.[:as]&.to_sym == a.name } end # No need to include models with no table, or subclassed models if their parent is already in the list hm_models.reject! { |m| !m.table_exists? || hm_models.any? { |parent| parent != m && m < parent } } if hm_models.empty? puts "Missing any real indication as to which models \"has_many\" this polymorphic BT in model #{a.active_record.name}:" puts " belongs_to :#{a.name}, polymorphic: true" else puts "Having analysed all currently-loaded models to infer the various polymorphic has_many associations for #{model.name}, here are the current results:" puts "::Brick.polymorphics = { \"#{model.table_name}.#{a.name}\" => #{hm_models.map(&:name).inspect} }" puts 'If you add the above to your brick.rb, it will "cement" these options into place, and avoid this lookup process.' s.first[a.foreign_key.to_s] = [a.name, hm_models, true] end end else bt_key = a.foreign_key.is_a?(Array) ? a.foreign_key : a.foreign_key.to_s s.first[bt_key] = [a.name, a.klass] end else # This gets all forms of has_many and has_one if through # has_many :through or has_one :through is_invalid_source = nil begin if a.through_reflection.macro != :has_many # This HM goes through either a belongs_to or a has_one, so essentially a HOT? # Treat it like a belongs_to - just keyed on the association name instead of a foreign_key s.first[a.name] = [a.name, a.klass] next elsif !a.source_reflection # Had considered: a.active_record.reflect_on_association(a.source_reflection_name).nil? is_invalid_source = true end rescue is_invalid_source = true end if is_invalid_source puts "WARNING: HMT relationship :#{a.name} in model #{model.name} has invalid source :#{a.source_reflection_name}." next end else if !a..key?(:as) && a.klass.column_names.exclude?(a.foreign_key.to_s) = ", #{a..map { |k, v| "#{k.inspect} => #{v.inspect}" }.join(', ')}" if a..present? puts "WARNING: Model #{model.name} has this association: has_many :#{a.name}#{} which expects column #{a.foreign_key} to exist in table #{a.klass.table_name}. This column is missing." next end end s.last[a.name] = a end end # Mark has_manys that go to an associative ("join") table so that they are skipped in the UI, # as well as any possible polymorphic associations skip_hms = {} hms.each do |hmt| if (through = hmt.last.[:through]) # ::Brick.relations[hmt.last.through_reflection.table_name] skip_hms[through] = nil if hms[through] && model.is_brick? && hmt.last.klass != hmt.last.active_record && # Don't pull HMs for HMTs that point back to the same table hmt[1].source_reflection.belongs_to? # Don't pull HMs when the HMT is built from HM -> HM # End up with a hash of HMT names pointing to join-table associations model._br_associatives[hmt.first] = hms[through] # || hms["#{(opt = hmt.last.options)[:through].to_s.singularize}_#{opt[:source].to_s.pluralize}".to_sym] end end skip_hms.each { |k, _v| hms.delete(k) } model.instance_variable_set(:@_brick_bts_and_hms, [bts, hms]) # Cache and return this result end |
.get_possible_schemas ⇒ Object
114 115 116 117 118 119 120 121 |
# File 'lib/brick.rb', line 114 def get_possible_schemas if (possible_schemas = (multitenancy = ::Brick.config.schema_behavior&.[](:multitenant)) && multitenancy&.[](:schema_to_analyse)) possible_schemas = [possible_schemas] unless possible_schemas.is_a?(Array) possible_schema = possible_schemas.find { |ps| ::Brick.db_schemas.key?(ps) } end [possible_schema, possible_schemas, multitenancy] end |
.get_status_of_resources ⇒ Object
Identify built out routes, migrations, models, (and also soon controllers and views!) for each resource
3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 |
# File 'lib/brick/extensions.rb', line 3195 def get_status_of_resources rails_root = ::Rails.root.to_s migrations = if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{rails_root}/db/migrate") Dir["#{mig_path}/**/*.rb"].each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s| File.read(v).split("\n").each_with_index do |line, line_idx| # For all non-commented lines, look for any that have "create_table", "alter_table", or "drop_table" if !line.lstrip.start_with?('#') && (idx = (line.index('create_table ') || line.index('create_table('))&.+(13)) || (idx = (line.index('alter_table ') || line.index('alter_table('))&.+(12)) || (idx = (line.index('drop_table ') || line.index('drop_table('))&.+(11)) tbl = line[idx..-1].match(/([:'"\w\.]+)/)&.captures&.first if tbl v = v[(rails_root.length)..-1] if v.start_with?(rails_root) v = v[1..-1] if v.start_with?('/') s[tbl.tr(':\'"', '').pluralize] << [v, line_idx + 1] end end end end end abstract_activerecord_bases = ::Brick.eager_load_classes(true) rails_root = ::Rails.root.to_s models = ::Brick.relations.each_with_object({}) do |rel, s| next if rel.first.is_a?(Symbol) begin if (model = rel.last[:class_name]&.constantize) && (inh = ActiveRecord::Base._brick_inheriteds[model]&.join(':')) inh = inh[rails_root.length + 1..-1] if inh.start_with?(rails_root) s[rel.first] = [inh, model] end rescue end end ::Brick.relations.each_with_object([]) do |v, s| next if v.first.is_a?(Symbol) # || # Brick.config.exclude_tables.include?(v.first) tbl_parts = v.first.split('.') tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant res = tbl_parts.join('.') table_name = (model = models[res])&.last&.table_name table_name ||= begin v.last[:class_name].constantize.table_name rescue end model = model.first if model.is_a?(Array) s << [v.first, table_name || v.first, migrations&.fetch(res, nil), model] end end |
.has_ones=(hos) ⇒ Object
Associations to treat as a has_one
509 510 511 512 513 514 515 516 517 518 519 520 |
# File 'lib/brick.rb', line 509 def has_ones=(hos) if hos hos = hos.call if hos.is_a?(Proc) hos = hos.to_a unless hos.is_a?(Array) hos = [hos] unless hos.empty? || hos.first.is_a?(Array) # Translate to being nested hashes Brick.config.has_ones = hos&.each_with_object(Hash.new { |h, k| h[k] = {} }) do |v, s| s[v.first][v[1]] = v[2] if v[1] s end end end |
.hmts=(assocs) ⇒ Object
Associations for which to auto-create a has_many _, through: _
530 531 532 |
# File 'lib/brick.rb', line 530 def hmts=(assocs) Brick.config.hmts = assocs end |
.is_apartment_excluded_table(tbl) ⇒ Object
3343 3344 3345 3346 3347 3348 3349 |
# File 'lib/brick/extensions.rb', line 3343 def is_apartment_excluded_table(tbl) if Object.const_defined?('Apartment') tbl_klass = (tnp = ::Brick.config.table_name_prefixes&.find { |k, _v| tbl.start_with?(k) }) ? +"#{tnp.last}::" : +'' tbl_klass << tbl[tnp&.first&.length || 0..-1].singularize.camelize Apartment.excluded_models&.include?(tbl_klass) end end |
.is_geography?(val) ⇒ Boolean
321 322 323 |
# File 'lib/brick.rb', line 321 def is_geography?(val) val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12] end |
.json_columns=(cols) ⇒ Object
541 542 543 |
# File 'lib/brick.rb', line 541 def json_columns=(cols) Brick.config.json_columns = cols end |
.license=(key) ⇒ Object
581 582 583 |
# File 'lib/brick.rb', line 581 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
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 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 |
# File 'lib/brick.rb', line 592 def load_additional_references return if @_additional_references_loaded || ::Brick.config.mode != :on relations = ::Brick.relations if (ars = ::Brick.config.additional_references) || ::Brick.config.polymorphics is_optional = ActiveRecord.version >= ::Gem::Version.new('5.0') if ars ars.each do |ar| fk = ar.length < 5 ? [nil, +ar[0], ar[1], nil, +ar[2]] : [ar[0], +ar[1], ar[2], ar[3], +ar[4], ar[5]] ::Brick._add_bt_and_hm(fk, relations, false, is_optional) end end if (polys = ::Brick.config.polymorphics) if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.key?(schema) ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema) end missing_stis = {} polys.each do |k, v| table_name, poly = k.split('.') v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").each_with_object([]) { |result, s| s << result['typ'] if result['typ'] } v.each do |type| # Allow polymorphic BT to relate to an STI subclass base_type = ::Brick.config.sti_namespace_prefixes["::#{type}"] || ::Brick.config.sti_namespace_prefixes.find { |k, _v| k.end_with?('::') && type.start_with?(k[2..-1]) }&.last&.[](2..-1) if relations.key?(primary_table = (base_type || type).underscore.pluralize) ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, type, # Polymorphic class is_optional) elsif relations.present? missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type) end end end unless missing_stis.empty? print " You might be missing an STI namespace prefix entry for these tables: #{missing_stis.keys.join(', ')}. In config/initializers/brick.rb appropriate entries would look something like: Brick.sti_namespace_prefixes = {" puts missing_stis.map { |_k, missing_sti| "\n '#{missing_sti}' => 'SomeParentModel'" }.join(',') puts " } (Just trade out SomeParentModel with some more appropriate one.)" end end @_additional_references_loaded = true end # Find associative tables that can be set up for has_many :through ::Brick.relations.each do |key, tbl| next if key.is_a?(Symbol) tbl_cols = tbl[:cols].keys fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = [fk.last[:assoc_name], fk.last[:inverse_table]] if fk.last[:is_bt]; s } # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has # foreign keys then it can act as an associative table and thus be used with has_many :through. if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config. || []) - (tbl[:pkey].values.first || [])).length.zero? fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last } end end end |
.load_with_retry(loader, autoloaded = nil) ⇒ Object
Some classes (like Phlex::Testing::Rails) will successfully auto-load after a retry
708 709 710 711 712 713 714 715 716 717 718 719 |
# File 'lib/brick.rb', line 708 def load_with_retry(loader, autoloaded = nil) autoloaded ||= loader.send(:autoloaded_dirs).dup begin loader.eager_load rescue Zeitwerk::SetupRequired # This is fine -- we eager load what can be eager loaded rescue Zeitwerk::NameError if autoloaded != (new_auto = loader.send(:autoloaded_dirs)) load_with_retry(loader, new_auto.dup) # Try one more time and it could come together end end end |
.metadata_columns=(value) ⇒ Object
455 456 457 |
# File 'lib/brick.rb', line 455 def (value) Brick.config. = value end |
.mode=(setting) ⇒ Object
326 327 328 |
# File 'lib/brick.rb', line 326 def mode=(setting) Brick.config.mode = setting end |
.model_descrips=(descrips) ⇒ Object
DSL templates for individual models to provide prettier descriptions of objects
552 553 554 |
# File 'lib/brick.rb', line 552 def model_descrips=(descrips) Brick.config.model_descrips = descrips end |
.models_inherit_from=(value) ⇒ Object
434 435 436 |
# File 'lib/brick.rb', line 434 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
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/brick.rb', line 184 def namify(name, action = nil) has_uppers = name =~ /[A-Z]+/ has_lowers = name =~ /[a-z]+/ name.downcase! if has_uppers && action == :downcase if name.include?(' ') # All uppers or all lowers? if !has_uppers || !has_lowers name.titleize.tr(' ', '_') else # Mixed uppers and lowers -- just remove existing spaces name.tr(' ', '') end else action == :underscore ? name.underscore : name end end |
.nested_attributes=(anaf) ⇒ Object
Associations upon which to add #accepts_nested_attributes_for logic
524 525 526 |
# File 'lib/brick.rb', line 524 def nested_attributes=(anaf) Brick.config.nested_attributes = anaf end |
.non_tenanted_models ⇒ Object
If multitenancy is enabled, a list of non-tenanted “global” models
179 180 181 |
# File 'lib/brick.rb', line 179 def non_tenanted_models @pending_models ||= {} end |
.not_nullables=(value) ⇒ Object
460 461 462 |
# File 'lib/brick.rb', line 460 def not_nullables=(value) Brick.config.not_nullables = value end |
.order=(value) ⇒ Object
485 486 487 |
# File 'lib/brick.rb', line 485 def order=(value) Brick.config.order = value end |
.path_prefix=(path) ⇒ Object
Any path prefixing to apply to all auto-generated Brick routes
332 333 334 |
# File 'lib/brick.rb', line 332 def path_prefix=(path) Brick.config.path_prefix = path end |
.polymorphics=(polys) ⇒ Object
Polymorphic associations
535 536 537 538 |
# File 'lib/brick.rb', line 535 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)
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/brick.rb', line 147 def relations return {} if (ch = ::ActiveRecord::Base.connection_handler).respond_to?(:connection_pool_list) && ch.connection_pool_list.blank? # Key our list of relations for this connection off of the connection pool's object_id cp_objid = ActiveRecord::Base.connection_pool.object_id @relations ||= {} # Have we already seen this database? (Perhaps we're connecting to a replica?) @relations[cp_objid] ||= if (db_name = ActiveRecord::Base.connection&.instance_variable_get(:@config)&.fetch(:database, nil)) && !@relations.key?(cp_objid) && (existing = @relations.find { |_k, v| v.fetch(:db_name, nil) == db_name }) existing.last else Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } }.tap do |h| h[:db_name] = db_name if db_name end end 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.
565 566 567 |
# File 'lib/brick.rb', line 565 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
569 570 571 |
# File 'lib/brick.rb', line 569 def schema_behaviour=(behavior) Brick.schema_behavior = behavior end |
.serializer ⇒ Object
Get the Brick serializer used by all threads.
669 670 671 |
# File 'lib/brick.rb', line 669 def serializer Brick.config.serializer end |
.serializer=(value) ⇒ Object
Set the Brick serializer. This setting affects all threads.
663 664 665 |
# File 'lib/brick.rb', line 663 def serializer=(value) Brick.config.serializer = value end |
.set_db_schema(params = nil) ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/brick.rb', line 123 def set_db_schema(params = nil) # If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into # a different tenant. If so then don't allow schema navigation. if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && apartment_multitenant current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)') .first['current_schemas'][1..-2] .split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first is_show_schema_list = current_schema == ::Brick.default_schema schema = (is_show_schema_list && params && params['_brick_schema']) || ::Brick.default_schema chosen = if is_show_schema_list && ::Brick.db_schemas&.key?(schema) Apartment::Tenant.switch!(schema) schema elsif ::Brick.test_schema is_show_schema_list = true Apartment::Tenant.switch!(::Brick.test_schema) ::Brick.test_schema else current_schema # Just return the current schema end end [chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list] end |
.sidescroll=(scroll) ⇒ Object
546 547 548 |
# File 'lib/brick.rb', line 546 def sidescroll=(scroll) Brick.config.sidescroll = scroll end |
.skip_database_views=(value) ⇒ Object
424 425 426 |
# File 'lib/brick.rb', line 424 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
503 504 505 |
# File 'lib/brick.rb', line 503 def skip_index_hms=(value) Brick.config.skip_index_hms = value end |
.sti_models ⇒ Object
101 102 103 |
# File 'lib/brick.rb', line 101 def sti_models @sti_models ||= {} end |
.sti_namespace_prefixes=(snp) ⇒ Object
Module prefixes to build out and associate with specific base STI models
558 559 560 |
# File 'lib/brick.rb', line 558 def sti_namespace_prefixes=(snp) Brick.config.sti_namespace_prefixes = snp end |
.sti_type_column=(type_col) ⇒ Object
573 574 575 |
# File 'lib/brick.rb', line 573 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
444 445 446 |
# File 'lib/brick.rb', line 444 def table_name_prefixes=(value) Brick.config.table_name_prefixes = value end |
.treat_as_module=(value) ⇒ Object
Causes Decidim to work with this line: Brick.treat_as_module = [‘Decidim::ContentBlocks’]
450 451 452 |
# File 'lib/brick.rb', line 450 def treat_as_module=(value) Brick.config.treat_as_module = value end |
.unexclude_column(table, col) ⇒ Object
316 317 318 319 |
# File 'lib/brick.rb', line 316 def unexclude_column(table, col) puts "Unexcluding #{table}.#{col}" true end |