Module: Brick

Defined in:
lib/brick/util.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/model_generator.rb,
lib/brick/frameworks/rails/controller.rb,
lib/generators/brick/install_generator.rb,
lib/brick/extensions.rb,
lib/brick.rb

Overview

:nodoc:

Defined Under Namespace

Modules: Cucumber, Extensions, Rails, RouteSet, Serializers, Util, VERSION Classes: Config, InstallGenerator, JoinArray, JoinHash, ModelGenerator

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.db_schemasObject

Returns the value of attribute db_schemas.



89
90
91
# File 'lib/brick.rb', line 89

def db_schemas
  @db_schemas
end

Class Method Details

._add_bt_and_hm(fk, relations = nil) ⇒ Object



941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
# File 'lib/brick/extensions.rb', line 941

def _add_bt_and_hm(fk, relations = nil)
  relations ||= ::Brick.relations
  bt_assoc_name = fk[1].underscore
  bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')

  bts = (relation = relations.fetch(fk[0], nil))&.fetch(:fks) { relation[:fks] = {} }
  primary_table = (is_class = fk[2].is_a?(Hash) && fk[2].key?(:class)) ? (primary_class = fk[2][:class].constantize).table_name : fk[2]
  hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class

  unless (cnstr_name = fk[3])
    # For any appended references (those that come from config), arrive upon a definitely unique constraint name
    cnstr_base = cnstr_name = "(brick) #{fk[0]}_#{is_class ? fk[2][:class].underscore : fk[2]}"
    cnstr_added_num = 1
    cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
    missing = []
    missing << fk[0] unless relations.key?(fk[0])
    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[0]][:cols]).key?(fk[1])
      columns = cols.map { |k, v| "#{k} (#{v.first.split(' ').first})" }
      puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
      return
    end
    if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && 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
  if (assoc_bt = bts[cnstr_name])
    assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[1]] : assoc_bt[:fk].concat(fk[1])
    assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[1]}"
  else
    assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[1], assoc_name: bt_assoc_name, inverse_table: primary_table }
  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[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }

  cnstr_name = "hm_#{cnstr_name}"
  if (assoc_hm = hms.fetch(cnstr_name, nil))
    assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
    assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
    assoc_hm[:inverse] = assoc_bt
  else
    assoc_hm = hms[cnstr_name] = { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0], inverse: assoc_bt }
    hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
    hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
  end
  assoc_bt[:inverse] = assoc_hm
  # hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
end

.additional_references=(ars) ⇒ Object

Additional table associations to use (Think of these as virtual foreign keys perhaps)



222
223
224
225
226
227
228
229
# File 'lib/brick.rb', line 222

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

.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.

Yields:



323
324
325
326
327
# File 'lib/brick.rb', line 323

def config
  @config ||= Brick::Config.instance
  yield @config if block_given?
  @config
end

.enable_controllers=(value) ⇒ Object

Switches Brick auto-controllers on or off, for all threads



153
154
155
# File 'lib/brick.rb', line 153

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.

Returns:

  • (Boolean)


160
161
162
# File 'lib/brick.rb', line 160

def enable_controllers?
  !!Brick.config.enable_controllers
end

.enable_models=(value) ⇒ Object

Switches Brick auto-models on or off, for all threads



140
141
142
# File 'lib/brick.rb', line 140

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.

Returns:

  • (Boolean)


147
148
149
# File 'lib/brick.rb', line 147

def enable_models?
  !!Brick.config.enable_models
end

.enable_routes=(value) ⇒ Object

Switches Brick auto-routes on or off, for all threads



179
180
181
# File 'lib/brick.rb', line 179

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.

Returns:

  • (Boolean)


186
187
188
# File 'lib/brick.rb', line 186

def enable_routes?
  !!Brick.config.enable_routes
end

.enable_views=(value) ⇒ Object

Switches Brick auto-views on or off, for all threads



166
167
168
# File 'lib/brick.rb', line 166

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.

Returns:

  • (Boolean)


173
174
175
# File 'lib/brick.rb', line 173

def enable_views?
  !!Brick.config.enable_views
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)



234
235
236
237
238
239
240
241
# File 'lib/brick.rb', line 234

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



196
197
198
# File 'lib/brick.rb', line 196

def exclude_tables=(value)
  Brick.config.exclude_tables = value
end

.gem_versionObject

Returns Brick’s ‘::Gem::Version`, convenient for comparisons. This is recommended over `::Brick::VERSION::STRING`.



304
305
306
# File 'lib/brick.rb', line 304

def gem_version
  ::Gem::Version.new(VERSION::STRING)
end

.get_bts_and_hms(model) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/brick.rb', line 104

def get_bts_and_hms(model)
  bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
    next if !const_defined?(a.name.to_s.singularize.camelize) && ::Brick.config.exclude_tables.include?(a.plural_name)

    # So that we can map an association name to any special alias name used in an AREL query
    ans = (model._assoc_names[a.name] ||= [])
    ans << a.klass unless ans.include?(a.klass)
    case a.macro
    when :belongs_to
      s.first[a.foreign_key] = [a.name, a.klass]
    when :has_many, :has_one # This gets has_many as well as has_many :through
      # %%% weed out ones that don't have an available model to reference
      s.last[a.name] = a
    end
  end
  # Mark has_manys that go to an associative ("join") table so that they are skipped in the UI,
  # as well as any possible polymorphic associations
  skip_hms = {}
  associatives = hms.each_with_object({}) do |hmt, s|
    if (through = hmt.last.options[:through])
      skip_hms[through] = nil
      s[hmt.first] = hms[through] # End up with a hash of HMT names pointing to join-table associations
    elsif hmt.last.inverse_of.nil?
      puts "SKIPPING #{hmt.last.name.inspect}"
      # %%% If we don't do this then below associative.name will find that associative is nil
      skip_hms[hmt.last.name] = nil
    end
  end
  skip_hms.each do |k, _v|
    puts hms.delete(k).inspect
  end
  [bts, hms, associatives]
end

.has_ones=(hos) ⇒ Object

Associations to treat as a has_one



251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/brick.rb', line 251

def has_ones=(hos)
  if hos
    hos = hos.call if hos.is_a?(Proc)
    hos = hos.to_a unless hos.is_a?(Array)
    hos = [hos] unless hos.empty? || hos.first.is_a?(Array)
    # Translate to being nested hashes
    Brick.config.has_ones = hos&.each_with_object(Hash.new { |h, k| h[k] = {} }) do |v, s|
      s[v.first][v[1]] = v[2] if v[1]
      s
    end
  end
end

.load_additional_referencesObject

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



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/brick.rb', line 279

def load_additional_references
  return if @_additional_references_loaded

  if (ars = ::Brick.config.additional_references)
    ars.each { |fk| ::Brick._add_bt_and_hm(fk[0..2]) }
    @_additional_references_loaded = true
  end

  # Find associative tables that can be set up for has_many :through
  ::Brick.relations.each do |_key, tbl|
    tbl_cols = tbl[:cols].keys
    fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = [fk.last[:assoc_name], fk.last[:inverse_table]] if fk.last[:is_bt]; s }
    # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
    # foreign keys then it can act as an associative table and thus be used with has_many :through.
    if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config. || []) - (tbl[:pkey].values.first || [])).length.zero?
      fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
    end
  end
end

.metadata_columns=(value) ⇒ Object



211
212
213
# File 'lib/brick.rb', line 211

def (value)
  Brick.config. = value
end

.model_descrips=(descrips) ⇒ Object

DSL templates for individual models to provide prettier descriptions of objects



266
267
268
# File 'lib/brick.rb', line 266

def model_descrips=(descrips)
  Brick.config.model_descrips = descrips
end

.models_inherit_from=(value) ⇒ Object



201
202
203
# File 'lib/brick.rb', line 201

def models_inherit_from=(value)
  Brick.config.models_inherit_from = value
end

.not_nullables=(value) ⇒ Object



216
217
218
# File 'lib/brick.rb', line 216

def not_nullables=(value)
  Brick.config.not_nullables = value
end

.relationsObject

All tables and views (what Postgres calls “relations” including column and foreign key info)



97
98
99
100
101
102
# File 'lib/brick.rb', line 97

def relations
  connections = Brick.instance_variable_get(:@relations) ||
    Brick.instance_variable_set(:@relations, (connections = {}))
  # Key our list of relations for this connection off of the connection pool's object_id
  (connections[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } })
end

.serializerObject

Get the Brick serializer used by all threads.



316
317
318
# File 'lib/brick.rb', line 316

def serializer
  Brick.config.serializer
end

.serializer=(value) ⇒ Object

Set the Brick serializer. This setting affects all threads.



310
311
312
# File 'lib/brick.rb', line 310

def serializer=(value)
  Brick.config.serializer = value
end

.set_db_schema(params) ⇒ Object



91
92
93
94
# File 'lib/brick.rb', line 91

def set_db_schema(params)
  schema = params['_brick_schema'] || 'public'
  ActiveRecord::Base.connection.execute("SET SEARCH_PATH='#{schema}';") if schema && ::Brick.db_schemas&.include?(schema)
end

.skip_database_views=(value) ⇒ Object



191
192
193
# File 'lib/brick.rb', line 191

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



245
246
247
# File 'lib/brick.rb', line 245

def skip_index_hms=(value)
  Brick.config.skip_index_hms = value
end

.sti_modelsObject



84
85
86
# File 'lib/brick.rb', line 84

def self.sti_models
  @sti_models ||= {}
end

.sti_namespace_prefixes=(snp) ⇒ Object

Module prefixes to build out and associate with specific base STI models



272
273
274
# File 'lib/brick.rb', line 272

def sti_namespace_prefixes=(snp)
  Brick.config.sti_namespace_prefixes = snp
end

.table_name_prefixes=(value) ⇒ Object



206
207
208
# File 'lib/brick.rb', line 206

def table_name_prefixes=(value)
  Brick.config.table_name_prefixes = value
end

.versionObject



330
331
332
# File 'lib/brick.rb', line 330

def version
  VERSION::STRING
end