Module: Brick
- Defined in:
- lib/brick/util.rb,
lib/brick.rb,
lib/brick/config.rb,
lib/brick/join_array.rb,
lib/brick/version_number.rb,
lib/brick/serializers/json.rb,
lib/brick/serializers/yaml.rb,
lib/brick/frameworks/cucumber.rb,
lib/brick/frameworks/rails/engine.rb,
lib/generators/brick/models_generator.rb,
lib/generators/brick/install_generator.rb,
lib/generators/brick/migrations_generator.rb,
lib/brick/extensions.rb
Overview
:nodoc:
Defined Under Namespace
Modules: Cucumber, Extensions, Rails, RouteSet, Serializers, Util, VERSION Classes: Config, InstallGenerator, JoinArray, JoinHash, MigrationsGenerator, ModelsGenerator
Constant Summary collapse
- ALL_API_ACTIONS =
[:index, :show, :create, :update, :destroy]
- CURRENCY_SYMBOLS =
'$£¢₵€₠ƒ¥₿₩₪₹₫₴₱₲₳₸₺₼₽៛₡₢₣₤₥₦₧₨₭₮₯₰₶₷₻₾'
- ADD_CONST_MISSING =
lambda do 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 || self.name.split('::')).length > 1 # Classic mode begin return self._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 ... 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)) return possible end end end class_name = ::Brick.namify(requested) relations = ::Brick.relations # 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 full_class_name = +'' full_class_name << "::#{(split_self_name&.first && split_self_name.join('::')) || self.name}" unless self == Object # (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}" if plural_class_name == 'BrickOpenapi' || ( (::Brick.config.add_status || ::Brick.config.add_orphans) && plural_class_name == 'BrickGem' ) || 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 # if it's a controller and no match or a model doesn't really use the same table name, eager load all models and try to find a model class of the right name. 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 !is_tnp_module && self.const_defined?(schema_name) # 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 # 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.
-
.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
-
.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
-
.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_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.
- .is_apartment_excluded_table(tbl) ⇒ Object
- .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.
-
.non_tenanted_models ⇒ Object
If multitenancy is enabled, a list of non-tenanted “global” models.
- .not_nullables=(value) ⇒ Object
- .order=(value) ⇒ Object
-
.path_prefix=(path) ⇒ Object
Any path prefixing to apply to all auto-generated Brick routes.
-
.polymorphics=(polys) ⇒ Object
Polymorphic associations.
-
.relations ⇒ Object
All tables and views (what Postgres calls “relations” including column and foreign key info).
-
.schema_behavior=(behavior) ⇒ Object
Database schema to use when analysing existing data, such as deriving a list of polymorphic classes for polymorphics in which it wasn’t originally specified.
-
.schema_behaviour=(behavior) ⇒ Object
For any Brits out there.
-
.serializer ⇒ Object
Get the Brick serializer used by all threads.
-
.serializer=(value) ⇒ Object
Set the Brick serializer.
- .set_db_schema(params = nil) ⇒ Object
- .skip_database_views=(value) ⇒ Object
-
.skip_index_hms=(value) ⇒ Object
Skip showing counts for these specific has_many associations when building auto-generated #index views.
- .sti_models ⇒ Object
-
.sti_namespace_prefixes=(snp) ⇒ Object
Module prefixes to build out and associate with specific base STI models.
- .sti_type_column=(type_col) ⇒ Object
- .table_name_prefixes=(value) ⇒ Object
- .unexclude_column(table, col) ⇒ Object
- .version ⇒ Object
Class Attribute Details
.auto_models ⇒ Object
Returns the value of attribute auto_models.
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 |
.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
2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 |
# File 'lib/brick/extensions.rb', line 2881 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.fetch(:isView, nil) }.keys.sort puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)" return end unless (cols = relations[fk[1]][:cols]).key?(fk[2]) || (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
3146 3147 3148 |
# File 'lib/brick/extensions.rb', line 3146 def _class_pk(dotted_name, multitenant) Object.const_get((multitenant ? [dotted_name.split('.').last] : dotted_name.split('.')).map { |nm| "::#{nm.singularize.camelize}" }.join).primary_key end |
.additional_references=(ars) ⇒ Object
Additional table associations to use (Think of these as virtual foreign keys perhaps)
424 425 426 427 428 429 430 431 |
# File 'lib/brick.rb', line 424 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
526 527 528 |
# File 'lib/brick.rb', line 526 def always_load_fields=(field_set) Brick.config.always_load_fields = field_set end |
.apartment_default_tenant ⇒ Object
150 151 152 |
# File 'lib/brick.rb', line 150 def apartment_default_tenant Apartment.default_tenant || 'public' end |
.apartment_multitenant ⇒ Object
143 144 145 146 147 148 |
# File 'lib/brick.rb', line 143 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
383 384 385 |
# File 'lib/brick.rb', line 383 def api_filter Brick.config.api_filter end |
.api_filter=(proc) ⇒ Object
378 379 380 |
# File 'lib/brick.rb', line 378 def api_filter=(proc) Brick.config.api_filter = proc end |
.api_root=(path) ⇒ Object
363 364 365 |
# File 'lib/brick.rb', line 363 def api_root=(path) Brick.config.api_roots = [path] end |
.api_roots ⇒ Object
373 374 375 |
# File 'lib/brick.rb', line 373 def api_roots Brick.config.api_roots end |
.api_roots=(paths) ⇒ Object
368 369 370 |
# File 'lib/brick.rb', line 368 def api_roots=(paths) Brick.config.api_roots = paths end |
.apply_double_underscore_patch ⇒ Object
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 |
# File 'lib/brick.rb', line 708 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 |
.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.
615 616 617 618 619 |
# File 'lib/brick.rb', line 615 def config @config ||= Brick::Config.instance yield @config if block_given? @config end |
.controllers_inherit_from=(value) ⇒ Object
403 404 405 |
# File 'lib/brick.rb', line 403 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
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 |
# File 'lib/brick.rb', line 669 def ctrl_to_klass(ctrl_path, res_names = {}) klass = nil sti_type = nil if res_names.empty? ::Brick.relations.each_with_object({}) do |v, s| v_parts = v.first.split('.') v_parts.shift if v_parts.first == 'public' res_names[v_parts.join('.')] = v.first end end c_path_parts = ctrl_path.split('/') 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.
435 436 437 438 439 440 |
# File 'lib/brick.rb', line 435 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
518 519 520 |
# File 'lib/brick.rb', line 518 def default_route_fallback=(resource_name) Brick.config.default_route_fallback = resource_name end |
.display_classes(prefix, rels, max_length) ⇒ Object
660 661 662 663 664 665 666 |
# File 'lib/brick.rb', line 660 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
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 |
# File 'lib/brick.rb', line 626 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
358 359 360 |
# File 'lib/brick.rb', line 358 def enable_api Brick.config.enable_api end |
.enable_api=(path) ⇒ Object
353 354 355 |
# File 'lib/brick.rb', line 353 def enable_api=(path) Brick.config.enable_api = path end |
.enable_controllers=(value) ⇒ Object
Switches Brick auto-controllers on or off, for all threads
315 316 317 |
# File 'lib/brick.rb', line 315 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.
322 323 324 |
# File 'lib/brick.rb', line 322 def enable_controllers? !!Brick.config.enable_controllers end |
.enable_models=(value) ⇒ Object
Switches Brick auto-models on or off, for all threads
302 303 304 |
# File 'lib/brick.rb', line 302 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.
309 310 311 |
# File 'lib/brick.rb', line 309 def enable_models? !!Brick.config.enable_models end |
.enable_routes=(value) ⇒ Object
Switches Brick auto-routes on or off, for all threads
341 342 343 |
# File 'lib/brick.rb', line 341 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.
348 349 350 |
# File 'lib/brick.rb', line 348 def enable_routes? !!Brick.config.enable_routes end |
.enable_views=(value) ⇒ Object
Switches Brick auto-views on or off, for all threads
328 329 330 |
# File 'lib/brick.rb', line 328 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.
335 336 337 |
# File 'lib/brick.rb', line 335 def enable_views? !!Brick.config.enable_views end |
.ensure_unique(name, *sources) ⇒ Object
3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 |
# File 'lib/brick/extensions.rb', line 3054 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
280 281 282 283 |
# File 'lib/brick.rb', line 280 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)
450 451 452 453 454 455 456 457 |
# File 'lib/brick.rb', line 450 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
393 394 395 |
# File 'lib/brick.rb', line 393 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
3139 3140 3141 3142 3143 3144 |
# File 'lib/brick/extensions.rb', line 3139 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
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 |
# File 'lib/brick/extensions.rb', line 3077 def find_orphans(multi_schema) is_default_schema = multi_schema&.==(::Brick.apartment_default_tenant) relations.each_with_object([]) do |v, s| frn_tbl = v.first next if (relation = v.last).key?(:isView) || config.exclude_tables.include?(frn_tbl) || !(for_pk = (relation[:pkey].values.first&.first)) is_default_frn_schema = !is_default_schema && multi_schema && ((frn_parts = frn_tbl.split('.')).length > 1 && frn_parts.first)&.==(::Brick.apartment_default_tenant) relation[:fks].select { |_k, assoc| assoc[:is_bt] }.each do |_k, bt| begin if bt.key?(:polymorphic) pri_pk = for_pk pri_tables = Brick.config.polymorphics["#{frn_tbl}.#{bt[:fk]}"] .each_with_object(Hash.new { |h, k| h[k] = [] }) do |pri_class, s| s[Object.const_get(pri_class).table_name] << pri_class end fk_id_col = "#{bt[:fk]}_id" fk_type_col = "#{bt[:fk]}_type" selects = [] pri_tables.each do |pri_tbl, pri_types| # Skip if database is multitenant, we're not focused on "public", and the foreign and primary tables # are both in the "public" schema next if is_default_frn_schema && ((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant) selects << "SELECT '#{pri_tbl}' AS pri_tbl, frn.#{fk_type_col} AS pri_type, frn.#{fk_id_col} AS pri_id, frn.#{for_pk} AS frn_id FROM #{frn_tbl} AS frn LEFT OUTER JOIN #{pri_tbl} AS pri ON pri.#{pri_pk} = frn.#{fk_id_col} WHERE frn.#{fk_type_col} IN (#{ pri_types.map { |pri_type| "'#{pri_type}'" }.join(', ') }) AND frn.#{bt[:fk]}_id IS NOT NULL AND pri.#{pri_pk} IS NULL\n" end ActiveRecord::Base.execute_sql(selects.join("UNION ALL\n")).each do |o| entry = [frn_tbl, o['frn_id'], o['pri_type'], o['pri_id'], fk_id_col] entry << o['pri_tbl'] if (pri_class = Object.const_get(o['pri_type'])) != pri_class.base_class s << entry end else # Skip if database is multitenant, we're not focused on "public", and the foreign and primary tables # are both in the "public" schema pri_tbl = bt.key?(:inverse_table) && bt[:inverse_table] next if is_default_frn_schema && ((pri_parts = pri_tbl&.split('.'))&.length > 1 && pri_parts.first)&.==(::Brick.apartment_default_tenant) pri_pk = relations[pri_tbl].fetch(:pkey, nil)&.values&.first&.first || _class_pk(pri_tbl, multi_schema) ActiveRecord::Base.execute_sql( "SELECT frn.#{bt[:fk]} AS pri_id, frn.#{for_pk} AS frn_id FROM #{frn_tbl} AS frn LEFT OUTER JOIN #{pri_tbl} AS pri ON pri.#{pri_pk} = frn.#{bt[:fk]} WHERE frn.#{bt[:fk]} IS NOT NULL AND pri.#{pri_pk} IS NULL ORDER BY 1, 2" ).each { |o| s << [frn_tbl, o['frn_id'], pri_tbl, o['pri_id'], bt[:fk]] } end rescue StandardError => err puts "Strange -- #{err.inspect}" end end end end |
.gem_version ⇒ Object
Returns Brick’s ‘::Gem::Version`, convenient for comparisons. This is recommended over `::Brick::VERSION::STRING`.
596 597 598 |
# File 'lib/brick.rb', line 596 def gem_version ::Gem::Version.new(VERSION::STRING) end |
.get_bts_and_hms(model, recalculate = nil) ⇒ Object
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 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 |
# File 'lib/brick.rb', line 176 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 next unless a.polymorphic? || (!a.belongs_to? && (through = a.[:through])) || (a.klass && ::Brick.config.exclude_tables.exclude?(a.klass.table_name) && (!a.belongs_to? || model_cols[a.foreign_key]&.type == pk_type) ) 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 subclassed models if their parent is already in the list hm_models.reject! { |m| 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 # 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_status_of_resources ⇒ Object
Identify built out routes, migrations, models, (and also soon controllers and views!) for each resource
3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 |
# File 'lib/brick/extensions.rb', line 3006 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| 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.map do |k, v| # next if Brick.config.exclude_tables.include?(k) tbl_parts = k.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[:class_name].constantize.table_name rescue end model = model.first if model.is_a?(Array) [k, table_name || k, migrations&.fetch(res, nil), model] end end |
.has_ones=(hos) ⇒ Object
Associations to treat as a has_one
467 468 469 470 471 472 473 474 475 476 477 478 |
# File 'lib/brick.rb', line 467 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 |
.is_apartment_excluded_table(tbl) ⇒ Object
3150 3151 3152 3153 3154 3155 3156 |
# File 'lib/brick/extensions.rb', line 3150 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 |
.json_columns=(cols) ⇒ Object
487 488 489 |
# File 'lib/brick.rb', line 487 def json_columns=(cols) Brick.config.json_columns = cols end |
.license=(key) ⇒ Object
522 523 524 |
# File 'lib/brick.rb', line 522 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
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 |
# File 'lib/brick.rb', line 533 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| 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
647 648 649 650 651 652 653 654 655 656 657 658 |
# File 'lib/brick.rb', line 647 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
413 414 415 |
# File 'lib/brick.rb', line 413 def (value) Brick.config. = value end |
.mode=(setting) ⇒ Object
290 291 292 |
# File 'lib/brick.rb', line 290 def mode=(setting) Brick.config.mode = setting end |
.model_descrips=(descrips) ⇒ Object
DSL templates for individual models to provide prettier descriptions of objects
493 494 495 |
# File 'lib/brick.rb', line 493 def model_descrips=(descrips) Brick.config.model_descrips = descrips end |
.models_inherit_from=(value) ⇒ Object
398 399 400 |
# File 'lib/brick.rb', line 398 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
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/brick.rb', line 160 def namify(name, action = nil) has_uppers = name =~ /[A-Z]+/ has_lowers = name =~ /[a-z]+/ name.downcase! if has_uppers && action == :downcase if name.include?(' ') # All uppers or all lowers? if !has_uppers || !has_lowers name.titleize.tr(' ', '_') else # Mixed uppers and lowers -- just remove existing spaces name.tr(' ', '') end else action == :underscore ? name.underscore : name end end |
.non_tenanted_models ⇒ Object
If multitenancy is enabled, a list of non-tenanted “global” models
155 156 157 |
# File 'lib/brick.rb', line 155 def non_tenanted_models @pending_models ||= {} end |
.not_nullables=(value) ⇒ Object
418 419 420 |
# File 'lib/brick.rb', line 418 def not_nullables=(value) Brick.config.not_nullables = value end |
.order=(value) ⇒ Object
443 444 445 |
# File 'lib/brick.rb', line 443 def order=(value) Brick.config.order = value end |
.path_prefix=(path) ⇒ Object
Any path prefixing to apply to all auto-generated Brick routes
296 297 298 |
# File 'lib/brick.rb', line 296 def path_prefix=(path) Brick.config.path_prefix = path end |
.polymorphics=(polys) ⇒ Object
Polymorphic associations
481 482 483 484 |
# File 'lib/brick.rb', line 481 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)
135 136 137 138 139 140 141 |
# File 'lib/brick.rb', line 135 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 (@relations ||= {})[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } } end |
.schema_behavior=(behavior) ⇒ Object
Database schema to use when analysing existing data, such as deriving a list of polymorphic classes for polymorphics in which it wasn’t originally specified.
506 507 508 |
# File 'lib/brick.rb', line 506 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
510 511 512 |
# File 'lib/brick.rb', line 510 def schema_behaviour=(behavior) Brick.schema_behavior = behavior end |
.serializer ⇒ Object
Get the Brick serializer used by all threads.
608 609 610 |
# File 'lib/brick.rb', line 608 def serializer Brick.config.serializer end |
.serializer=(value) ⇒ Object
Set the Brick serializer. This setting affects all threads.
602 603 604 |
# File 'lib/brick.rb', line 602 def serializer=(value) Brick.config.serializer = value end |
.set_db_schema(params = nil) ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/brick.rb', line 113 def set_db_schema(params = nil) # If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into # a different tenant. If so then don't allow schema navigation. chosen = if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && (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 = (apartment_multitenant && current_schema == ::Brick.default_schema)) && (schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) && ::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 [chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list] end |
.skip_database_views=(value) ⇒ Object
388 389 390 |
# File 'lib/brick.rb', line 388 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
461 462 463 |
# File 'lib/brick.rb', line 461 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
499 500 501 |
# File 'lib/brick.rb', line 499 def sti_namespace_prefixes=(snp) Brick.config.sti_namespace_prefixes = snp end |
.sti_type_column=(type_col) ⇒ Object
514 515 516 |
# File 'lib/brick.rb', line 514 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
408 409 410 |
# File 'lib/brick.rb', line 408 def table_name_prefixes=(value) Brick.config.table_name_prefixes = value end |
.unexclude_column(table, col) ⇒ Object
284 285 286 287 |
# File 'lib/brick.rb', line 284 def unexclude_column(table, col) puts "Unexcluding #{table}.#{col}" true end |