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/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, SeedsGenerator
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 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 !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
- ._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
-
.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.
-
.nested_attributes=(anaf) ⇒ Object
Associations upon which to add #accepts_nested_attributes_for logic.
-
.non_tenanted_models ⇒ Object
If multitenancy is enabled, a list of non-tenanted “global” models.
- .not_nullables=(value) ⇒ Object
- .order=(value) ⇒ Object
-
.path_prefix=(path) ⇒ Object
Any path prefixing to apply to all auto-generated Brick routes.
-
.polymorphics=(polys) ⇒ Object
Polymorphic associations.
-
.relations ⇒ Object
All tables and views (what Postgres calls “relations” including column and foreign key info).
-
.schema_behavior=(behavior) ⇒ Object
Database schema to use when analysing existing data, such as deriving a list of polymorphic classes for polymorphics in which it wasn’t originally specified.
-
.schema_behaviour=(behavior) ⇒ Object
For any Brits out there.
-
.serializer ⇒ Object
Get the Brick serializer used by all threads.
-
.serializer=(value) ⇒ Object
Set the Brick serializer.
- .set_db_schema(params = nil) ⇒ Object
- .sidescroll=(scroll) ⇒ Object
- .skip_database_views=(value) ⇒ Object
-
.skip_index_hms=(value) ⇒ Object
Skip showing counts for these specific has_many associations when building auto-generated #index views.
- .sti_models ⇒ Object
-
.sti_namespace_prefixes=(snp) ⇒ Object
Module prefixes to build out and associate with specific base STI models.
- .sti_type_column=(type_col) ⇒ Object
- .table_name_prefixes=(value) ⇒ Object
- .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
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 3002 3003 3004 3005 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 |
# File 'lib/brick/extensions.rb', line 2918 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
3183 3184 3185 |
# File 'lib/brick/extensions.rb', line 3183 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
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 |
# File 'lib/brick.rb', line 764 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)
431 432 433 434 435 436 437 438 |
# File 'lib/brick.rb', line 431 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
544 545 546 |
# File 'lib/brick.rb', line 544 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
390 391 392 |
# File 'lib/brick.rb', line 390 def api_filter Brick.config.api_filter end |
.api_filter=(proc) ⇒ Object
385 386 387 |
# File 'lib/brick.rb', line 385 def api_filter=(proc) Brick.config.api_filter = proc end |
.api_root=(path) ⇒ Object
370 371 372 |
# File 'lib/brick.rb', line 370 def api_root=(path) Brick.config.api_roots = [path] end |
.api_roots ⇒ Object
380 381 382 |
# File 'lib/brick.rb', line 380 def api_roots Brick.config.api_roots end |
.api_roots=(paths) ⇒ Object
375 376 377 |
# File 'lib/brick.rb', line 375 def api_roots=(paths) Brick.config.api_roots = paths end |
.apply_double_underscore_patch ⇒ Object
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 |
# File 'lib/brick.rb', line 726 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.
633 634 635 636 637 |
# File 'lib/brick.rb', line 633 def config @config ||= Brick::Config.instance yield @config if block_given? @config end |
.controllers_inherit_from=(value) ⇒ Object
410 411 412 |
# File 'lib/brick.rb', line 410 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
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 |
# File 'lib/brick.rb', line 687 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.
442 443 444 445 446 447 |
# File 'lib/brick.rb', line 442 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
536 537 538 |
# File 'lib/brick.rb', line 536 def default_route_fallback=(resource_name) Brick.config.default_route_fallback = resource_name end |
.display_classes(prefix, rels, max_length) ⇒ Object
678 679 680 681 682 683 684 |
# File 'lib/brick.rb', line 678 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
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 |
# File 'lib/brick.rb', line 644 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
365 366 367 |
# File 'lib/brick.rb', line 365 def enable_api Brick.config.enable_api end |
.enable_api=(path) ⇒ Object
360 361 362 |
# File 'lib/brick.rb', line 360 def enable_api=(path) Brick.config.enable_api = path end |
.enable_controllers=(value) ⇒ Object
Switches Brick auto-controllers on or off, for all threads
322 323 324 |
# File 'lib/brick.rb', line 322 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.
329 330 331 |
# File 'lib/brick.rb', line 329 def enable_controllers? !!Brick.config.enable_controllers end |
.enable_models=(value) ⇒ Object
Switches Brick auto-models on or off, for all threads
309 310 311 |
# File 'lib/brick.rb', line 309 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.
316 317 318 |
# File 'lib/brick.rb', line 316 def enable_models? !!Brick.config.enable_models end |
.enable_routes=(value) ⇒ Object
Switches Brick auto-routes on or off, for all threads
348 349 350 |
# File 'lib/brick.rb', line 348 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.
355 356 357 |
# File 'lib/brick.rb', line 355 def enable_routes? !!Brick.config.enable_routes end |
.enable_views=(value) ⇒ Object
Switches Brick auto-views on or off, for all threads
335 336 337 |
# File 'lib/brick.rb', line 335 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.
342 343 344 |
# File 'lib/brick.rb', line 342 def enable_views? !!Brick.config.enable_views end |
.ensure_unique(name, *sources) ⇒ Object
3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 |
# File 'lib/brick/extensions.rb', line 3091 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
287 288 289 290 |
# File 'lib/brick.rb', line 287 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)
457 458 459 460 461 462 463 464 |
# File 'lib/brick.rb', line 457 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
400 401 402 |
# File 'lib/brick.rb', line 400 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
3176 3177 3178 3179 3180 3181 |
# File 'lib/brick/extensions.rb', line 3176 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
3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 |
# File 'lib/brick/extensions.rb', line 3114 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`.
614 615 616 |
# File 'lib/brick.rb', line 614 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 279 280 281 282 283 284 285 |
# 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 unless a.polymorphic? || (!a.belongs_to? && (through = a.[:through])) || (a.klass && ::Brick.config.exclude_tables.exclude?(a.klass.table_name) && (!a.belongs_to? || (same_type = (fk_type = model_cols[a.foreign_key]&.type) == pk_type)) ) if same_type == false # We really do want to test specifically for false here, and not nil! puts "WARNING: Foreign key column #{a.klass.table_name}.#{a.foreign_key} is #{fk_type}, but the primary key it relates to, #{a.active_record.table_name}.#{a.active_record.primary_key}, is #{pk_type}. These columns should both be of the same type." end next end if a.belongs_to? if a.polymorphic? rel_poly_bt = relations[model.table_name][:fks].find { |_k, fk| fk[:assoc_name] == a.name.to_s } if (primary_tables = rel_poly_bt&.last&.fetch(:inverse_table, [])).is_a?(Array) models = rel_poly_bt[1][:polymorphic]&.map { |table| table.singularize.camelize.constantize } s.first[a.foreign_key.to_s] = [a.name, models, true] else # This will come up when using Devise invitable when invited_by_class_name is not # specified because in that circumstance it adds a polymorphic :invited_by association, # along with appropriate invited_by_type and invited_by_id columns. # See if any currently-loaded models have a has_many association over to this polymorphic belongs_to hm_models = ActiveRecord::Base.descendants.select do |m| m.reflect_on_all_associations.any? { |assoc| !assoc.belongs_to? && assoc.[:as]&.to_sym == a.name } end # No need to include 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
3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 |
# File 'lib/brick/extensions.rb', line 3043 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
474 475 476 477 478 479 480 481 482 483 484 485 |
# File 'lib/brick.rb', line 474 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
3187 3188 3189 3190 3191 3192 3193 |
# File 'lib/brick/extensions.rb', line 3187 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
500 501 502 |
# File 'lib/brick.rb', line 500 def json_columns=(cols) Brick.config.json_columns = cols end |
.license=(key) ⇒ Object
540 541 542 |
# File 'lib/brick.rb', line 540 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
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 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 |
# File 'lib/brick.rb', line 551 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
665 666 667 668 669 670 671 672 673 674 675 676 |
# File 'lib/brick.rb', line 665 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
420 421 422 |
# File 'lib/brick.rb', line 420 def (value) Brick.config. = value end |
.mode=(setting) ⇒ Object
297 298 299 |
# File 'lib/brick.rb', line 297 def mode=(setting) Brick.config.mode = setting end |
.model_descrips=(descrips) ⇒ Object
DSL templates for individual models to provide prettier descriptions of objects
511 512 513 |
# File 'lib/brick.rb', line 511 def model_descrips=(descrips) Brick.config.model_descrips = descrips end |
.models_inherit_from=(value) ⇒ Object
405 406 407 |
# File 'lib/brick.rb', line 405 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 |
.nested_attributes=(anaf) ⇒ Object
Associations upon which to add #accepts_nested_attributes_for logic
489 490 491 |
# File 'lib/brick.rb', line 489 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
155 156 157 |
# File 'lib/brick.rb', line 155 def non_tenanted_models @pending_models ||= {} end |
.not_nullables=(value) ⇒ Object
425 426 427 |
# File 'lib/brick.rb', line 425 def not_nullables=(value) Brick.config.not_nullables = value end |
.order=(value) ⇒ Object
450 451 452 |
# File 'lib/brick.rb', line 450 def order=(value) Brick.config.order = value end |
.path_prefix=(path) ⇒ Object
Any path prefixing to apply to all auto-generated Brick routes
303 304 305 |
# File 'lib/brick.rb', line 303 def path_prefix=(path) Brick.config.path_prefix = path end |
.polymorphics=(polys) ⇒ Object
Polymorphic associations
494 495 496 497 |
# File 'lib/brick.rb', line 494 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.
524 525 526 |
# File 'lib/brick.rb', line 524 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
528 529 530 |
# File 'lib/brick.rb', line 528 def schema_behaviour=(behavior) Brick.schema_behavior = behavior end |
.serializer ⇒ Object
Get the Brick serializer used by all threads.
626 627 628 |
# File 'lib/brick.rb', line 626 def serializer Brick.config.serializer end |
.serializer=(value) ⇒ Object
Set the Brick serializer. This setting affects all threads.
620 621 622 |
# File 'lib/brick.rb', line 620 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 |
.sidescroll=(scroll) ⇒ Object
505 506 507 |
# File 'lib/brick.rb', line 505 def sidescroll=(scroll) Brick.config.sidescroll = scroll end |
.skip_database_views=(value) ⇒ Object
395 396 397 |
# File 'lib/brick.rb', line 395 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
468 469 470 |
# File 'lib/brick.rb', line 468 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
517 518 519 |
# File 'lib/brick.rb', line 517 def sti_namespace_prefixes=(snp) Brick.config.sti_namespace_prefixes = snp end |
.sti_type_column=(type_col) ⇒ Object
532 533 534 |
# File 'lib/brick.rb', line 532 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
415 416 417 |
# File 'lib/brick.rb', line 415 def table_name_prefixes=(value) Brick.config.table_name_prefixes = value end |
.unexclude_column(table, col) ⇒ Object
291 292 293 294 |
# File 'lib/brick.rb', line 291 def unexclude_column(table, col) puts "Unexcluding #{table}.#{col}" true end |