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/salesforce_schema.rb,
lib/generators/brick/migrations_generator.rb,
lib/generators/brick/salesforce_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, SalesforceMigrationsGenerator, SalesforceSchema, 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.
-
.table_name_lookup ⇒ Object
Returns the value of attribute table_name_lookup.
-
.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
- ._brick_index(tbl_name, mode = nil, separator = nil, relation = nil) ⇒ 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
-
.defer_references_for_generation=(drfg) ⇒ Object
References to disregard when auto-building migrations, models, and seeds.
- .display_classes(prefix, rels, max_length) ⇒ Object
- .drfgs ⇒ 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, delimiter, *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
- .omit_empty_tables_in_dropdown=(setting) ⇒ 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
-
.swagger_endpoint(url, name) ⇒ Object
Add a swagger endpoint in the same kind of way that RSwag UI does.
- .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 |
.table_name_lookup ⇒ Object
Returns the value of attribute table_name_lookup.
109 110 111 |
# File 'lib/brick.rb', line 109 def table_name_lookup @table_name_lookup 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
3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 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 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 |
# File 'lib/brick/extensions.rb', line 3160 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}", nil, 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 |
._brick_index(tbl_name, mode = nil, separator = nil, relation = nil) ⇒ Object
3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 |
# File 'lib/brick/extensions.rb', line 3429 def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil) separator ||= '_' res_name = (tbl_name_parts = tbl_name.split('.'))[0..-2].first res_name << '.' if res_name (res_name ||= +'') << (relation || ::Brick.relations.fetch(tbl_name, nil)&.fetch(:resource, nil) || tbl_name_parts.last) res_parts = ((mode == :singular) ? res_name.singularize : res_name).split('.') res_parts.shift if ::Brick.apartment_multitenant && res_parts.length > 1 && res_parts.first == ::Brick.apartment_default_tenant if (aps = relation&.fetch(:auto_prefixed_schema, nil)) && res_parts.last.start_with?(aps) last_part = res_parts.last[aps.length..-1] aps = aps[0..-2] if aps[-1] == '_' res_parts[-1] = aps res_parts << last_part end path_prefix = [] if ::Brick.config.path_prefix res_parts.unshift(::Brick.config.path_prefix) path_prefix << ::Brick.config.path_prefix end index = res_parts.map(&:underscore).join(separator) index = index.tr('_', 'x') if separator == 'x' # Rails applies an _index suffix to that route when the resource name isn't something plural index << '_index' if mode != :singular && separator == '_' && index == (path_prefix + [name&.underscore&.tr('/', '_') || '_']).join(separator) index end |
._class_pk(dotted_name, multitenant) ⇒ Object
3463 3464 3465 |
# File 'lib/brick/extensions.rb', line 3463 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
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 |
# File 'lib/brick.rb', line 850 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)
488 489 490 491 492 493 494 495 |
# File 'lib/brick.rb', line 488 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
626 627 628 |
# File 'lib/brick.rb', line 626 def always_load_fields=(field_set) Brick.config.always_load_fields = field_set end |
.apartment_default_tenant ⇒ Object
177 178 179 |
# File 'lib/brick.rb', line 177 def apartment_default_tenant Apartment.default_tenant || 'public' end |
.apartment_multitenant ⇒ Object
170 171 172 173 174 175 |
# File 'lib/brick.rb', line 170 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
441 442 443 |
# File 'lib/brick.rb', line 441 def api_filter Brick.config.api_filter end |
.api_filter=(proc) ⇒ Object
436 437 438 |
# File 'lib/brick.rb', line 436 def api_filter=(proc) Brick.config.api_filter = proc end |
.api_root=(path) ⇒ Object
421 422 423 |
# File 'lib/brick.rb', line 421 def api_root=(path) Brick.config.api_roots = [path] end |
.api_roots ⇒ Object
431 432 433 |
# File 'lib/brick.rb', line 431 def api_roots Brick.config.api_roots end |
.api_roots=(paths) ⇒ Object
426 427 428 |
# File 'lib/brick.rb', line 426 def api_roots=(paths) Brick.config.api_roots = paths end |
.apply_double_underscore_patch ⇒ Object
812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 |
# File 'lib/brick.rb', line 812 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
3283 3284 3285 |
# File 'lib/brick/extensions.rb', line 3283 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.
717 718 719 720 721 |
# File 'lib/brick.rb', line 717 def config @config ||= Brick::Config.instance yield @config if block_given? @config end |
.controllers_inherit_from=(value) ⇒ Object
461 462 463 |
# File 'lib/brick.rb', line 461 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
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 808 809 810 |
# File 'lib/brick.rb', line 771 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.
514 515 516 517 518 519 |
# File 'lib/brick.rb', line 514 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
614 615 616 |
# File 'lib/brick.rb', line 614 def default_route_fallback=(resource_name) Brick.config.default_route_fallback = resource_name end |
.defer_references_for_generation=(drfg) ⇒ Object
References to disregard when auto-building migrations, models, and seeds
499 500 501 502 503 504 |
# File 'lib/brick.rb', line 499 def defer_references_for_generation=(drfg) if drfg drfg = [drfg] unless drfg.empty? || drfg.first.is_a?(Array) Brick.config.defer_references_for_generation = drfg end end |
.display_classes(prefix, rels, max_length) ⇒ Object
762 763 764 765 766 767 768 |
# File 'lib/brick.rb', line 762 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 |
.drfgs ⇒ Object
506 507 508 509 510 |
# File 'lib/brick.rb', line 506 def drfgs (::Brick.config.defer_references_for_generation || []).each_with_object({}) do |drfg, s| s[drfg.first] = [drfg[1..-1]] end end |
.eager_load_classes(do_ar_abstract_bases = false) ⇒ Object
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 |
# File 'lib/brick.rb', line 728 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
416 417 418 |
# File 'lib/brick.rb', line 416 def enable_api Brick.config.enable_api end |
.enable_api=(path) ⇒ Object
411 412 413 |
# File 'lib/brick.rb', line 411 def enable_api=(path) Brick.config.enable_api = path end |
.enable_controllers=(value) ⇒ Object
Switches Brick auto-controllers on or off, for all threads
373 374 375 |
# File 'lib/brick.rb', line 373 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.
380 381 382 |
# File 'lib/brick.rb', line 380 def enable_controllers? !!Brick.config.enable_controllers end |
.enable_models=(value) ⇒ Object
Switches Brick auto-models on or off, for all threads
360 361 362 |
# File 'lib/brick.rb', line 360 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.
367 368 369 |
# File 'lib/brick.rb', line 367 def enable_models? !!Brick.config.enable_models end |
.enable_routes=(value) ⇒ Object
Switches Brick auto-routes on or off, for all threads
399 400 401 |
# File 'lib/brick.rb', line 399 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.
406 407 408 |
# File 'lib/brick.rb', line 406 def enable_routes? !!Brick.config.enable_routes end |
.enable_views=(value) ⇒ Object
Switches Brick auto-views on or off, for all threads
386 387 388 |
# File 'lib/brick.rb', line 386 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.
393 394 395 |
# File 'lib/brick.rb', line 393 def enable_views? !!Brick.config.enable_views end |
.ensure_unique(name, delimiter, *sources) ⇒ Object
3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 |
# File 'lib/brick/extensions.rb', line 3341 def ensure_unique(name, delimiter, *sources) base = name delimiter ||= '_' # By default ends up building this regex: /_(\d+)$/ if (added_num = name.slice!(Regexp.new("#{delimiter}(\d+)$"))) added_num = added_num[1..-1].to_i else added_num = 1 end while ( name = "#{base}#{delimiter}#{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
328 329 330 331 |
# File 'lib/brick.rb', line 328 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)
529 530 531 532 533 534 535 536 |
# File 'lib/brick.rb', line 529 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
451 452 453 |
# File 'lib/brick.rb', line 451 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
3456 3457 3458 3459 3460 3461 |
# File 'lib/brick/extensions.rb', line 3456 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
3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 |
# File 'lib/brick/extensions.rb', line 3366 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`.
698 699 700 |
# File 'lib/brick.rb', line 698 def gem_version ::Gem::Version.new(VERSION::STRING) end |
.get_bts_and_hms(model, recalculate = nil) ⇒ Object
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 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/brick.rb', line 203 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 bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s| 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 if (through = a.[:through]) && !a.through_reflection puts "WARNING: In model #{model.name}, unable to utilise: has_many #{a.name}, through: #{through.inspect}" end primary_klass = if a.belongs_to? a.polymorphic? ? nil : a.klass else a.active_record end a_pk = a..fetch(:primary_key, nil)&.to_s || primary_klass&.primary_key # Was: a.klass.primary_key if !a.polymorphic? && (a.belongs_to? || (through && (thr = a.through_reflection))) && !((kls = thr&.klass || a.klass) && ::Brick.config.exclude_tables.exclude?(kls.table_name) && (!a.belongs_to? || ((fk_type = a.foreign_key.is_a?(Array) ? a.foreign_key.map { |fk_part| model_cols[fk_part.to_s].type } : model_cols[a.foreign_key.to_s].type) && (primary_cols = primary_klass.columns_hash) && (pk_type = a_pk.is_a?(Array) ? a_pk.map { |pk_part| primary_cols[pk_part.to_s].type } : primary_cols[a_pk].type) && (same_type = (pk_type == fk_type)) ) ) ) # unless a.polymorphic? || (!a.belongs_to? && (through = a.options[:through])) || # (a.klass && ::Brick.config.exclude_tables.exclude?(a.klass.table_name) && # (!a.belongs_to? || (same_type = (fk_type = model_cols[a.foreign_key.to_s]&.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, #{primary_klass.table_name}.#{a_pk}, 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 " due to invalid source #{a.source_reflection_name.inspect}." next end else this_fks = (this_fk = a.foreign_key).is_a?(Array) ? this_fk.uniq : [this_fk.to_s] if !a..key?(:as) && (this_fks - a.klass.column_names).length.positive? = ", #{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
115 116 117 118 119 120 121 122 |
# File 'lib/brick.rb', line 115 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
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 3331 3332 3333 3334 3335 3336 3337 3338 3339 |
# File 'lib/brick/extensions.rb', line 3290 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
546 547 548 549 550 551 552 553 554 555 556 557 |
# File 'lib/brick.rb', line 546 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: _
567 568 569 |
# File 'lib/brick.rb', line 567 def hmts=(assocs) Brick.config.hmts = assocs end |
.is_apartment_excluded_table(tbl) ⇒ Object
3467 3468 3469 3470 3471 3472 3473 |
# File 'lib/brick/extensions.rb', line 3467 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
343 344 345 |
# File 'lib/brick.rb', line 343 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
578 579 580 |
# File 'lib/brick.rb', line 578 def json_columns=(cols) Brick.config.json_columns = cols end |
.license=(key) ⇒ Object
618 619 620 |
# File 'lib/brick.rb', line 618 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
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 |
# File 'lib/brick.rb', line 633 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
749 750 751 752 753 754 755 756 757 758 759 760 |
# File 'lib/brick.rb', line 749 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
477 478 479 |
# File 'lib/brick.rb', line 477 def (value) Brick.config. = value end |
.mode=(setting) ⇒ Object
348 349 350 |
# File 'lib/brick.rb', line 348 def mode=(setting) Brick.config.mode = setting end |
.model_descrips=(descrips) ⇒ Object
DSL templates for individual models to provide prettier descriptions of objects
589 590 591 |
# File 'lib/brick.rb', line 589 def model_descrips=(descrips) Brick.config.model_descrips = descrips end |
.models_inherit_from=(value) ⇒ Object
456 457 458 |
# File 'lib/brick.rb', line 456 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
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/brick.rb', line 187 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
561 562 563 |
# File 'lib/brick.rb', line 561 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
182 183 184 |
# File 'lib/brick.rb', line 182 def non_tenanted_models @pending_models ||= {} end |
.not_nullables=(value) ⇒ Object
482 483 484 |
# File 'lib/brick.rb', line 482 def not_nullables=(value) Brick.config.not_nullables = value end |
.omit_empty_tables_in_dropdown=(setting) ⇒ Object
622 623 624 |
# File 'lib/brick.rb', line 622 def omit_empty_tables_in_dropdown=(setting) Brick.config.omit_empty_tables_in_dropdown = setting end |
.order=(value) ⇒ Object
522 523 524 |
# File 'lib/brick.rb', line 522 def order=(value) Brick.config.order = value end |
.path_prefix=(path) ⇒ Object
Any path prefixing to apply to all auto-generated Brick routes
354 355 356 |
# File 'lib/brick.rb', line 354 def path_prefix=(path) Brick.config.path_prefix = path end |
.polymorphics=(polys) ⇒ Object
Polymorphic associations
572 573 574 575 |
# File 'lib/brick.rb', line 572 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)
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/brick.rb', line 148 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 rescue ActiveRecord::NoDatabaseError {} 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.
602 603 604 |
# File 'lib/brick.rb', line 602 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
606 607 608 |
# File 'lib/brick.rb', line 606 def schema_behaviour=(behavior) Brick.schema_behavior = behavior end |
.serializer ⇒ Object
Get the Brick serializer used by all threads.
710 711 712 |
# File 'lib/brick.rb', line 710 def serializer Brick.config.serializer end |
.serializer=(value) ⇒ Object
Set the Brick serializer. This setting affects all threads.
704 705 706 |
# File 'lib/brick.rb', line 704 def serializer=(value) Brick.config.serializer = value end |
.set_db_schema(params = nil) ⇒ Object
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/brick.rb', line 124 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
583 584 585 |
# File 'lib/brick.rb', line 583 def sidescroll=(scroll) Brick.config.sidescroll = scroll end |
.skip_database_views=(value) ⇒ Object
446 447 448 |
# File 'lib/brick.rb', line 446 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
540 541 542 |
# File 'lib/brick.rb', line 540 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
595 596 597 |
# File 'lib/brick.rb', line 595 def sti_namespace_prefixes=(snp) Brick.config.sti_namespace_prefixes = snp end |
.sti_type_column=(type_col) ⇒ Object
610 611 612 |
# File 'lib/brick.rb', line 610 def sti_type_column=(type_col) Brick.config.sti_type_column = (type_col.is_a?(String) ? { type_col => nil } : type_col) end |
.swagger_endpoint(url, name) ⇒ Object
Add a swagger endpoint in the same kind of way that RSwag UI does
338 339 340 341 |
# File 'lib/brick.rb', line 338 def swagger_endpoint(url, name) @swagger_endpoints ||= [] @swagger_endpoints << { url: url, name: name } end |
.table_name_prefixes=(value) ⇒ Object
466 467 468 |
# File 'lib/brick.rb', line 466 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’]
472 473 474 |
# File 'lib/brick.rb', line 472 def treat_as_module=(value) Brick.config.treat_as_module = value end |
.unexclude_column(table, col) ⇒ Object
332 333 334 335 |
# File 'lib/brick.rb', line 332 def unexclude_column(table, col) puts "Unexcluding #{table}.#{col}" true end |