brick 1.0.31 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/extensions.rb +342 -220
- data/lib/brick/frameworks/rails/engine.rb +27 -10
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +12 -6
- data/lib/generators/brick/install_generator.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d1e2f8b6ff7ca3fa7fcd129c33de3dfe6e674b6da154b9a64e094c65a3a152a
|
4
|
+
data.tar.gz: 30e39f62458a46854f56b2a6decbfe5ea42a4bb5325e81ed76f21400aae8e252
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14c6bae09e34f53126e2003cbc7256c4b32f4a4219d42f1f7e94ca83211debf3704fa6c170cd6231d89e26eb168f282d1dea7eff7150ef1a013364aeb4eb65c3
|
7
|
+
data.tar.gz: 7f33aa5c89be2e8401f4cda0ea6cecb6e01639934a2df1e7b00bd88d065bf3cc9b748bebb1691d51c1b04114fba3b1739a7820c7614f4eb84d52494bd4cc769e
|
data/lib/brick/extensions.rb
CHANGED
@@ -86,7 +86,11 @@ module ActiveRecord
|
|
86
86
|
descrip_col = (columns.map(&:name) - _brick_get_fks -
|
87
87
|
(::Brick.config.metadata_columns || []) -
|
88
88
|
[primary_key]).first
|
89
|
-
dsl = ::Brick.config.model_descrips[name] =
|
89
|
+
dsl = ::Brick.config.model_descrips[name] = if descrip_col
|
90
|
+
"[#{descrip_col}]"
|
91
|
+
elsif (pk_parts = self.primary_key.is_a?(Array) ? self.primary_key : [self.primary_key])
|
92
|
+
"#{name} ##{pk_parts.map { |pk_part| "[#{pk_part}]" }.join(', ')}"
|
93
|
+
end
|
90
94
|
end
|
91
95
|
dsl
|
92
96
|
end
|
@@ -104,7 +108,8 @@ module ActiveRecord
|
|
104
108
|
if ch == ']' # Time to process a bracketed thing?
|
105
109
|
parts = bracket_name.split('.')
|
106
110
|
first_parts = parts[0..-2].map do |part|
|
107
|
-
klass = klass.reflect_on_association(part_sym = part.to_sym)
|
111
|
+
klass = (orig_class = klass).reflect_on_association(part_sym = part.to_sym)&.klass
|
112
|
+
puts "Couldn't reference #{orig_class.name}##{part} that's part of the DSL \"#{dsl}\"." if klass.nil?
|
108
113
|
part_sym
|
109
114
|
end
|
110
115
|
parts = prefix + first_parts + [parts[-1]]
|
@@ -128,7 +133,7 @@ module ActiveRecord
|
|
128
133
|
end
|
129
134
|
else # With no DSL available, still put this prefix into the JoinArray so we can get primary key (ID) info from this table
|
130
135
|
x = prefix.each_with_object(build_array) { |v, s| s[v.to_sym] }
|
131
|
-
x[prefix
|
136
|
+
x[prefix.last] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
|
132
137
|
end
|
133
138
|
members
|
134
139
|
end
|
@@ -158,6 +163,7 @@ module ActiveRecord
|
|
158
163
|
bracket_name.split('.').each do |part|
|
159
164
|
obj_name += ".#{part}"
|
160
165
|
this_obj = caches.fetch(obj_name) { caches[obj_name] = this_obj&.send(part.to_sym) }
|
166
|
+
break if this_obj.nil?
|
161
167
|
end
|
162
168
|
this_obj&.to_s || ''
|
163
169
|
end
|
@@ -251,13 +257,12 @@ module ActiveRecord
|
|
251
257
|
names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
|
252
258
|
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
253
259
|
# The left side is the "JOIN" table
|
254
|
-
names += _recurse_arel(piece.left)
|
260
|
+
names += _recurse_arel(table = piece.left)
|
255
261
|
# The expression on the right side is the "ON" clause
|
256
262
|
# on = piece.right.expr
|
257
263
|
# # Find the table which is not ourselves, and thus must be the "path" that led us here
|
258
264
|
# parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
|
259
265
|
# binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
|
260
|
-
table = piece.left
|
261
266
|
if table.is_a?(Arel::Nodes::TableAlias)
|
262
267
|
alias_name = table.right
|
263
268
|
table = table.left
|
@@ -276,7 +281,8 @@ module ActiveRecord
|
|
276
281
|
@_brick_chains = {}
|
277
282
|
# The left side is the "FROM" table
|
278
283
|
# names += _recurse_arel(piece.left)
|
279
|
-
names << [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)]
|
284
|
+
names << (this_name = [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)])
|
285
|
+
(_brick_chains[this_name.first] ||= []) << this_name.last
|
280
286
|
# The right side is an array of all JOINs
|
281
287
|
piece.right.each { |join| names << _recurse_arel(join) }
|
282
288
|
end
|
@@ -303,6 +309,23 @@ module ActiveRecord
|
|
303
309
|
# , is_add_bts, is_add_hms
|
304
310
|
)
|
305
311
|
is_add_bts = is_add_hms = true
|
312
|
+
is_distinct = nil
|
313
|
+
wheres = {}
|
314
|
+
params.each do |k, v|
|
315
|
+
case (ks = k.split('.')).length
|
316
|
+
when 1
|
317
|
+
next unless klass._brick_get_fks.include?(k)
|
318
|
+
when 2
|
319
|
+
assoc_name = ks.first.to_sym
|
320
|
+
# Make sure it's a good association name and that the model has that column name
|
321
|
+
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
|
322
|
+
|
323
|
+
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
324
|
+
is_distinct = true
|
325
|
+
distinct!
|
326
|
+
end
|
327
|
+
wheres[k] = v.split(',')
|
328
|
+
end
|
306
329
|
|
307
330
|
# %%% Skip the metadata columns
|
308
331
|
if selects&.empty? # Default to all columns
|
@@ -311,7 +334,9 @@ module ActiveRecord
|
|
311
334
|
if (col_name = col.name) == 'class'
|
312
335
|
col_alias = ' AS _class'
|
313
336
|
end
|
314
|
-
|
337
|
+
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
338
|
+
cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col.name)&.first&.start_with?('xml')
|
339
|
+
selects << "\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
|
315
340
|
end
|
316
341
|
end
|
317
342
|
|
@@ -337,21 +362,6 @@ module ActiveRecord
|
|
337
362
|
end
|
338
363
|
end
|
339
364
|
|
340
|
-
wheres = {}
|
341
|
-
params.each do |k, v|
|
342
|
-
case (ks = k.split('.')).length
|
343
|
-
when 1
|
344
|
-
next unless klass._brick_get_fks.include?(k)
|
345
|
-
when 2
|
346
|
-
assoc_name = ks.first.to_sym
|
347
|
-
# Make sure it's a good association name and that the model has that column name
|
348
|
-
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
|
349
|
-
|
350
|
-
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
351
|
-
end
|
352
|
-
wheres[k] = v.split(',')
|
353
|
-
end
|
354
|
-
|
355
365
|
if join_array.present?
|
356
366
|
left_outer_joins!(join_array)
|
357
367
|
# Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
|
@@ -369,7 +379,9 @@ module ActiveRecord
|
|
369
379
|
v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
|
370
380
|
field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
|
371
381
|
|
372
|
-
|
382
|
+
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
383
|
+
is_xml = is_distinct && Brick.relations[sel_col.first.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
|
384
|
+
selects << "\"#{field_tbl_name}\".\"#{sel_col.last}\"#{'::text' if is_xml} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
|
373
385
|
v1[idx] << col_alias
|
374
386
|
end
|
375
387
|
|
@@ -397,6 +409,7 @@ module ActiveRecord
|
|
397
409
|
hm_counts.each do |k, hm|
|
398
410
|
associative = nil
|
399
411
|
count_column = if hm.options[:through]
|
412
|
+
# binding.pry if associatives[hm.name].nil?
|
400
413
|
fk_col = (associative = associatives[hm.name]).foreign_key
|
401
414
|
hm.foreign_key
|
402
415
|
else
|
@@ -420,11 +433,13 @@ module ActiveRecord
|
|
420
433
|
on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
|
421
434
|
end
|
422
435
|
join_clause = "LEFT OUTER
|
423
|
-
JOIN (SELECT #{selects.join(', ')}, COUNT(#{
|
436
|
+
JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
|
437
|
+
}) AS _ct_ FROM #{associative&.table_name || hm.klass.table_name
|
438
|
+
} GROUP BY #{(1..selects.length).to_a.join(', ')}) AS #{tbl_alias}"
|
424
439
|
joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
|
425
440
|
end
|
426
441
|
where!(wheres) unless wheres.empty?
|
427
|
-
limit(1000) # Don't want to get too carried away just yet
|
442
|
+
limit!(1000) # Don't want to get too carried away just yet
|
428
443
|
wheres unless wheres.empty? # Return the specific parameters that we did use
|
429
444
|
end
|
430
445
|
|
@@ -485,7 +500,9 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
|
|
485
500
|
alias _brick_autoload_module! autoload_module!
|
486
501
|
def autoload_module!(*args)
|
487
502
|
into, const_name, qualified_name, path_suffix = args
|
488
|
-
|
503
|
+
base_class_name = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)
|
504
|
+
base_class_name = "::#{base_class_name}" unless base_class_name.start_with?('::')
|
505
|
+
if (base_class = base_class_name&.constantize)
|
489
506
|
::Brick.sti_models[qualified_name] = { base: base_class }
|
490
507
|
# Build subclass and place it into the specially STI-namespaced module
|
491
508
|
into.const_set(const_name.to_sym, klass = Class.new(base_class))
|
@@ -506,8 +523,13 @@ end
|
|
506
523
|
Module.class_exec do
|
507
524
|
alias _brick_const_missing const_missing
|
508
525
|
def const_missing(*args)
|
509
|
-
if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) != self) ||
|
510
|
-
(self != Object && Object.const_defined?(args.first) &&
|
526
|
+
if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) && possible != self) ||
|
527
|
+
(self != Object && Object.const_defined?(args.first) &&
|
528
|
+
(
|
529
|
+
(possible = Object.const_get(args.first)) &&
|
530
|
+
(possible != self || (possible == self && possible.is_a?(Class)))
|
531
|
+
)
|
532
|
+
)
|
511
533
|
return possible
|
512
534
|
end
|
513
535
|
class_name = args.first.to_s
|
@@ -533,51 +555,64 @@ Module.class_exec do
|
|
533
555
|
result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
534
556
|
# Otherwise now it's up to us to fill in the gaps
|
535
557
|
# (Go over to underscores for a moment so that if we have something come in like VABCsController then the model name ends up as
|
536
|
-
#
|
558
|
+
# Vabc instead of VABC)
|
537
559
|
full_class_name = +''
|
538
560
|
full_class_name << "::#{self.name}" unless self == Object
|
539
561
|
full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
|
540
|
-
if (model = self.const_get(full_class_name))
|
562
|
+
if (plural_class_name == 'BrickSwagger' || model = self.const_get(full_class_name))
|
541
563
|
# 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.
|
542
564
|
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
543
565
|
end
|
544
566
|
elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
|
545
567
|
self == Object && # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing
|
546
|
-
schema_name = [(singular_table_name = class_name.underscore),
|
547
|
-
|
548
|
-
|
549
|
-
|
568
|
+
(schema_name = [(singular_table_name = class_name.underscore),
|
569
|
+
(table_name = singular_table_name.pluralize),
|
570
|
+
class_name,
|
571
|
+
(plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }&.camelize ||
|
572
|
+
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name))
|
550
573
|
# Build out a module for the schema if it's namespaced
|
551
|
-
schema_name = schema_name.camelize
|
574
|
+
# schema_name = schema_name.camelize
|
552
575
|
self.const_set(schema_name.to_sym, (built_module = Module.new))
|
553
576
|
|
554
577
|
[built_module, "module #{schema_name}; end\n"]
|
555
578
|
# # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
|
556
579
|
elsif ::Brick.enable_models?
|
580
|
+
# Custom inheritable Brick base model?
|
581
|
+
class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick')
|
557
582
|
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
558
583
|
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
559
584
|
|
560
|
-
|
585
|
+
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
586
|
+
self != Object # ... or otherwise already in some namespace?
|
561
587
|
schema_name = [(singular_schema_name = name.underscore),
|
562
588
|
(schema_name = singular_schema_name.pluralize),
|
563
589
|
name,
|
564
590
|
name.pluralize].find { |s| Brick.db_schemas.include?(s) }
|
565
591
|
end
|
566
|
-
|
567
592
|
plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
568
593
|
# If it's namespaced then we turn the first part into what would be a schema name
|
569
|
-
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
594
|
+
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
|
595
|
+
|
596
|
+
if base_model
|
597
|
+
schema_name = name.underscore # For the auto-STI namespace models
|
598
|
+
table_name = base_model.table_name
|
599
|
+
Object.send(:build_model, self, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
|
600
|
+
else
|
601
|
+
# Adjust for STI if we know of a base model for the requested model name
|
602
|
+
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
603
|
+
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
604
|
+
base_model.table_name
|
605
|
+
else
|
606
|
+
ActiveSupport::Inflector.pluralize(singular_table_name)
|
607
|
+
end
|
608
|
+
if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') &&
|
609
|
+
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
610
|
+
schema_name = Apartment.default_schema
|
611
|
+
end
|
612
|
+
# Maybe, just maybe there's a database table that will satisfy this need
|
613
|
+
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
614
|
+
Object.send(:build_model, schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
615
|
+
end
|
581
616
|
end
|
582
617
|
end
|
583
618
|
if result
|
@@ -593,7 +628,7 @@ Module.class_exec do
|
|
593
628
|
elsif self != Object
|
594
629
|
module_parent.const_missing(*args)
|
595
630
|
else
|
596
|
-
puts "MISSING!
|
631
|
+
puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
|
597
632
|
self._brick_const_missing(*args)
|
598
633
|
end
|
599
634
|
end
|
@@ -601,123 +636,39 @@ end
|
|
601
636
|
|
602
637
|
class Object
|
603
638
|
class << self
|
604
|
-
# alias _brick_const_missing const_missing
|
605
|
-
# def const_missing(*args)
|
606
|
-
# # return self.const_get(args.first) if self.const_defined?(args.first)
|
607
|
-
# # return Object.const_get(args.first) if Object.const_defined?(args.first) unless self == Object
|
608
|
-
# if self.const_defined?(args.first) && (possible = self.const_get(args.first)) != self
|
609
|
-
# return possible
|
610
|
-
# end
|
611
|
-
# if self != Object && Object.const_defined?(args.first) && (possible = Object.const_get(args.first)) != self
|
612
|
-
# return possible
|
613
|
-
# end
|
614
|
-
|
615
|
-
# class_name = args.first.to_s
|
616
|
-
# # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
617
|
-
# # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
618
|
-
# # that is, checking #qualified_name_for with: from_mod, const_name
|
619
|
-
# # If we want to support namespacing in the future, might have to utilise something like this:
|
620
|
-
# # path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
|
621
|
-
# # return self._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
|
622
|
-
# # If the file really exists, go and snag it:
|
623
|
-
# if !(is_found = ActiveSupport::Dependencies.search_for_file(class_name.underscore)) && (filepath = (self.name || class_name)&.split('::'))
|
624
|
-
# filepath = (filepath[0..-2] + [class_name]).join('/').underscore + '.rb'
|
625
|
-
# end
|
626
|
-
# if is_found
|
627
|
-
# return self._brick_const_missing(*args)
|
628
|
-
# elsif ActiveSupport::Dependencies.search_for_file(filepath) # Last-ditch effort to pick this thing up before we fill in the gaps on our own
|
629
|
-
# my_const = parent.const_missing(class_name) # ends up having: MyModule::MyClass
|
630
|
-
# return my_const
|
631
|
-
# end
|
632
|
-
# relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
|
633
|
-
# result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
|
634
|
-
# # Otherwise now it's up to us to fill in the gaps
|
635
|
-
# # (Go over to underscores for a moment so that if we have something come in like VABCsController then the model name ends up as
|
636
|
-
# # VAbc instead of VABC)
|
637
|
-
# if (model = Object.const_get(plural_class_name.underscore.singularize.camelize))
|
638
|
-
# # 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.
|
639
|
-
# build_controller(nil, class_name, plural_class_name, model, relations)
|
640
|
-
# end
|
641
|
-
# elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
|
642
|
-
# db_schema_name = [(singular_table_name = class_name.underscore),
|
643
|
-
# (table_name = singular_table_name.pluralize),
|
644
|
-
# class_name,
|
645
|
-
# (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }
|
646
|
-
# # Build out a module for the schema if it's namespaced
|
647
|
-
# schema_name = db_schema_name.camelize
|
648
|
-
# unless Object.const_defined?(schema_name.to_sym)
|
649
|
-
# Object.const_set(schema_name.to_sym, (built_module = Module.new))
|
650
|
-
# Brick.db_schemas[db_schema_name] = built_module
|
651
|
-
# [built_module, "module #{schema_name}; end\n"]
|
652
|
-
# end
|
653
|
-
# # # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
|
654
|
-
# # schema_name, model_name =
|
655
|
-
# # code = +''
|
656
|
-
# # mod_tree = +''
|
657
|
-
# # model_name.split('::')[0..-2].each do |mod_name|
|
658
|
-
# # mod_tree << "::#{mod_name}"
|
659
|
-
# # Module.new(mod_tree)
|
660
|
-
# # code << "module #{mod_tree}; end\n"
|
661
|
-
# # end
|
662
|
-
# elsif ::Brick.enable_models?
|
663
|
-
# # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
664
|
-
# # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
665
|
-
# plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
666
|
-
# singular_table_name = ActiveSupport::Inflector.underscore(model_name)
|
667
|
-
|
668
|
-
# # Adjust for STI if we know of a base model for the requested model name
|
669
|
-
# table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
670
|
-
# base_model.table_name
|
671
|
-
# else
|
672
|
-
# ActiveSupport::Inflector.pluralize(singular_table_name)
|
673
|
-
# end
|
674
|
-
|
675
|
-
# # Maybe, just maybe there's a database table that will satisfy this need
|
676
|
-
# if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
|
677
|
-
# build_model(nil, model_name, singular_table_name, table_name, relations, matching)
|
678
|
-
# end
|
679
|
-
# end
|
680
|
-
# if result
|
681
|
-
# built_class, code = result
|
682
|
-
# puts "\n#{code}"
|
683
|
-
# built_class
|
684
|
-
# elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}") && !schema_name
|
685
|
-
# # module_prefixes = type_name.split('::')
|
686
|
-
# # path = self.name.split('::')[0..-2] + []
|
687
|
-
# # module_prefixes.unshift('') unless module_prefixes.first.blank?
|
688
|
-
# # candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
|
689
|
-
# self._brick_const_missing(*args)
|
690
|
-
# elsif self != Object
|
691
|
-
# module_parent.const_missing(*args)
|
692
|
-
# else
|
693
|
-
# puts "MISSING! obj #{self.name}/#{schema_name} #{args.inspect} #{table_name}"
|
694
|
-
# self._brick_const_missing(*args)
|
695
|
-
# end
|
696
|
-
# end
|
697
639
|
|
698
640
|
private
|
699
641
|
|
700
|
-
def build_model(schema_name, model_name, singular_table_name, table_name, relations, matching)
|
701
|
-
full_name = if schema_name.
|
702
|
-
|
642
|
+
def build_model(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
643
|
+
full_name = if (::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_name == Apartment.default_schema)
|
644
|
+
relation = relations["#{schema_name}.#{matching}"]
|
645
|
+
inheritable_name || model_name
|
646
|
+
elsif schema_name.blank?
|
647
|
+
inheritable_name || model_name
|
703
648
|
else # Prefix the schema to the table name + prefix the schema namespace to the class name
|
704
|
-
schema_module =
|
705
|
-
|
706
|
-
|
649
|
+
schema_module = if schema_name.instance_of?(Module) # from an auto-STI namespace?
|
650
|
+
schema_name
|
651
|
+
else
|
652
|
+
matching = "#{schema_name}.#{matching}"
|
653
|
+
(Brick.db_schemas[schema_name] ||= self.const_get(schema_name.singularize.camelize))
|
654
|
+
end
|
655
|
+
"#{schema_module&.name}::#{inheritable_name || model_name}"
|
707
656
|
end
|
708
657
|
|
709
|
-
return if ((is_view = (relation
|
658
|
+
return if ((is_view = (relation ||= relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
|
710
659
|
::Brick.config.exclude_tables.include?(matching)
|
711
660
|
|
712
661
|
# Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
|
713
662
|
if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
|
714
|
-
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.
|
715
|
-
puts "Warning: Class name for a model that references table \"#{matching
|
663
|
+
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
|
664
|
+
puts "Warning: Class name for a model that references table \"#{matching
|
665
|
+
}\" should be \"#{ActiveSupport::Inflector.singularize(inheritable_name || model_name)}\"."
|
716
666
|
end
|
717
667
|
return
|
718
668
|
end
|
719
669
|
|
720
|
-
|
670
|
+
full_model_name = full_name.split('::').tap { |fn| fn[-1] = model_name }.join('::')
|
671
|
+
if (base_model = ::Brick.sti_models[full_model_name]&.fetch(:base, nil) || ::Brick.existing_stis[full_model_name]&.constantize)
|
721
672
|
is_sti = true
|
722
673
|
else
|
723
674
|
base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
|
@@ -725,9 +676,21 @@ class Object
|
|
725
676
|
hmts = nil
|
726
677
|
code = +"class #{full_name} < #{base_model.name}\n"
|
727
678
|
built_model = Class.new(base_model) do |new_model_class|
|
728
|
-
(schema_module || Object).const_set(model_name.to_sym, new_model_class)
|
679
|
+
(schema_module || Object).const_set((inheritable_name || model_name).to_sym, new_model_class)
|
680
|
+
if inheritable_name
|
681
|
+
new_model_class.define_singleton_method :inherited do |subclass|
|
682
|
+
super(subclass)
|
683
|
+
if subclass.name == model_name
|
684
|
+
puts "#{full_model_name} properly extends from #{full_name}"
|
685
|
+
else
|
686
|
+
puts "should be \"class #{model_name} < #{inheritable_name}\"\n (not \"#{subclass.name} < #{inheritable_name}\")"
|
687
|
+
end
|
688
|
+
end
|
689
|
+
self.abstract_class = true
|
690
|
+
code << " self.abstract_class = true\n"
|
691
|
+
end
|
729
692
|
# Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
|
730
|
-
code << " self.table_name = '#{self.table_name = matching}'\n"
|
693
|
+
code << " self.table_name = '#{self.table_name = matching}'\n" if inheritable_name || table_name != matching
|
731
694
|
|
732
695
|
# Override models backed by a view so they return true for #is_view?
|
733
696
|
# (Dynamically-created controllers and view templates for such models will then act in a read-only way)
|
@@ -755,6 +718,9 @@ class Object
|
|
755
718
|
code << " self.primary_key = #{pk_sym.inspect}\n"
|
756
719
|
end
|
757
720
|
_brick_primary_key(relation) # Set the newly-found PK in the instance variable
|
721
|
+
elsif (possible_pk = ActiveRecord::Base.get_primary_key(base_class.name)) && relation[:cols][possible_pk]
|
722
|
+
new_model_class.primary_key = (possible_pk = possible_pk.to_sym)
|
723
|
+
code << " self.primary_key = #{possible_pk.inspect}\n"
|
758
724
|
else
|
759
725
|
code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
|
760
726
|
end
|
@@ -792,10 +758,11 @@ class Object
|
|
792
758
|
end # class definition
|
793
759
|
# Having this separate -- will this now work out better?
|
794
760
|
built_model.class_exec do
|
795
|
-
hmts
|
761
|
+
hmts&.each do |hmt_fk, fks|
|
796
762
|
hmt_fk = hmt_fk.tr('.', '_')
|
797
763
|
fks.each do |fk|
|
798
|
-
|
764
|
+
# %%% Will not work with custom has_many name
|
765
|
+
through = ::Brick.config.schema_behavior[:multitenant] ? fk.first[:assoc_name] : fk.first[:inverse_table].tr('.', '_').pluralize
|
799
766
|
hmt_name = if fks.length > 1
|
800
767
|
if fks[0].first[:inverse][:assoc_name] == fks[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
|
801
768
|
"#{hmt_fk}_through_#{fk.first[:assoc_name]}"
|
@@ -807,10 +774,15 @@ class Object
|
|
807
774
|
else
|
808
775
|
hmt_fk
|
809
776
|
end
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
777
|
+
options = { through: through.to_sym }
|
778
|
+
if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
|
779
|
+
hmt_name = "#{hmt_name.singularize}_#{fk.first[:assoc_name]}"
|
780
|
+
# binding.pry if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
|
781
|
+
options[:class_name] = fk.first[:inverse_table].singularize.camelize
|
782
|
+
options[:foreign_key] = fk.first[:fk].to_sym
|
783
|
+
end
|
784
|
+
options[:source] = fk.last.to_sym unless hmt_name.singularize == fk.last
|
785
|
+
code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
|
814
786
|
self.send(:has_many, hmt_name.to_sym, **options)
|
815
787
|
end
|
816
788
|
end
|
@@ -832,6 +804,7 @@ class Object
|
|
832
804
|
else
|
833
805
|
assoc[:assoc_name]
|
834
806
|
end
|
807
|
+
options[:optional] = true if assoc.key?(:optional)
|
835
808
|
if assoc.key?(:polymorphic)
|
836
809
|
options[:polymorphic] = true
|
837
810
|
else
|
@@ -875,7 +848,11 @@ class Object
|
|
875
848
|
end
|
876
849
|
# Figure out if we need to specially call out the class_name and/or foreign key
|
877
850
|
# (and if either of those then definitely also a specific inverse_of)
|
878
|
-
|
851
|
+
if (singular_table_parts = singular_table_name.split('.')).length > 1 &&
|
852
|
+
::Brick.config.schema_behavior[:multitenant] && singular_table_parts.first == 'public'
|
853
|
+
singular_table_parts.shift
|
854
|
+
end
|
855
|
+
options[:class_name] = "::#{assoc[:primary_class]&.name || singular_table_parts.map(&:camelize).join('::')}" if need_class_name
|
879
856
|
# Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
|
880
857
|
if need_fk # Funky foreign key?
|
881
858
|
options[:foreign_key] = if assoc[:fk].is_a?(Array)
|
@@ -889,7 +866,6 @@ class Object
|
|
889
866
|
|
890
867
|
# Prepare a list of entries for "has_many :through"
|
891
868
|
if macro == :has_many
|
892
|
-
puts [inverse_table, relations[inverse_table].length].inspect
|
893
869
|
relations[inverse_table][:hmt_fks].each do |k, hmt_fk|
|
894
870
|
next if k == assoc[:fk]
|
895
871
|
|
@@ -905,19 +881,71 @@ class Object
|
|
905
881
|
def build_controller(namespace, class_name, plural_class_name, model, relations)
|
906
882
|
table_name = ActiveSupport::Inflector.underscore(plural_class_name)
|
907
883
|
singular_table_name = ActiveSupport::Inflector.singularize(table_name)
|
908
|
-
pk = model
|
884
|
+
pk = model&._brick_primary_key(relations.fetch(table_name, nil))
|
909
885
|
|
910
886
|
namespace_name = "#{namespace.name}::" if namespace
|
911
887
|
code = +"class #{namespace_name}#{class_name} < ApplicationController\n"
|
912
888
|
built_controller = Class.new(ActionController::Base) do |new_controller_class|
|
913
889
|
(namespace || Object).const_set(class_name.to_sym, new_controller_class)
|
914
890
|
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
891
|
+
unless (is_swagger = plural_class_name == 'BrickSwagger') # && request.format == :json)
|
892
|
+
code << " def index\n"
|
893
|
+
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
|
894
|
+
code << " @#{table_name}.brick_select(params)\n"
|
895
|
+
code << " end\n"
|
896
|
+
end
|
919
897
|
self.protect_from_forgery unless: -> { self.request.format.js? }
|
920
898
|
self.define_method :index do
|
899
|
+
if is_swagger
|
900
|
+
json = { 'openapi': '3.0.1', 'info': { 'title': 'API V1', 'version': 'v1' },
|
901
|
+
'servers': [
|
902
|
+
{ 'url': 'https://{defaultHost}', 'variables': { 'defaultHost': { 'default': 'www.example.com' } } }
|
903
|
+
]
|
904
|
+
}
|
905
|
+
json['paths'] = relations.inject({}) do |s, v|
|
906
|
+
s["/api/v1/#{v.first}"] = {
|
907
|
+
'get': {
|
908
|
+
'summary': "list #{v.first}",
|
909
|
+
'parameters': v.last[:cols].map { |k, v| { 'name' => k, 'schema': { 'type': v.first } } },
|
910
|
+
'responses': { '200': { 'description': 'successful' } }
|
911
|
+
}
|
912
|
+
}
|
913
|
+
# next if v.last[:isView]
|
914
|
+
|
915
|
+
s["/api/v1/#{v.first}/{id}"] = {
|
916
|
+
'patch': {
|
917
|
+
'summary': "update a #{v.first.singularize}",
|
918
|
+
'parameters': v.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
|
919
|
+
{ 'name' => k, 'schema': { 'type': v.first } }
|
920
|
+
end,
|
921
|
+
'responses': { '200': { 'description': 'successful' } }
|
922
|
+
}
|
923
|
+
# "/api/v1/books/{id}": {
|
924
|
+
# "parameters": [
|
925
|
+
# {
|
926
|
+
# "name": "id",
|
927
|
+
# "in": "path",
|
928
|
+
# "description": "id",
|
929
|
+
# "required": true,
|
930
|
+
# "schema": {
|
931
|
+
# "type": "string"
|
932
|
+
# }
|
933
|
+
# },
|
934
|
+
# {
|
935
|
+
# "name": "Authorization",
|
936
|
+
# "in": "header",
|
937
|
+
# "schema": {
|
938
|
+
# "type": "string"
|
939
|
+
# }
|
940
|
+
# }
|
941
|
+
# ],
|
942
|
+
}
|
943
|
+
s
|
944
|
+
end
|
945
|
+
# binding.pry
|
946
|
+
render inline: json.to_json, content_type: request.format
|
947
|
+
return
|
948
|
+
end
|
921
949
|
::Brick.set_db_schema(params)
|
922
950
|
if request.format == :csv # Asking for a template?
|
923
951
|
require 'csv'
|
@@ -946,7 +974,7 @@ class Object
|
|
946
974
|
@_brick_join_array = join_array
|
947
975
|
end
|
948
976
|
|
949
|
-
if model
|
977
|
+
if model&.primary_key
|
950
978
|
code << " def show\n"
|
951
979
|
code << (find_by_id = " id = params[:id]&.split(/[\\/,_]/)
|
952
980
|
id = id.first if id.is_a?(Array) && id.length == 1
|
@@ -961,7 +989,7 @@ class Object
|
|
961
989
|
end
|
962
990
|
|
963
991
|
# By default, views get marked as read-only
|
964
|
-
unless
|
992
|
+
unless is_swagger # model.readonly # (relation = relations[model.table_name]).key?(:isView)
|
965
993
|
code << " # (Define :new, :create)\n"
|
966
994
|
|
967
995
|
if model.primary_key
|
@@ -993,7 +1021,9 @@ class Object
|
|
993
1021
|
# return
|
994
1022
|
end
|
995
1023
|
|
996
|
-
|
1024
|
+
id = params[:id]&.split(/[\/,_]/)
|
1025
|
+
id = id.first if id.is_a?(Array) && id.length == 1
|
1026
|
+
instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(id)))
|
997
1027
|
obj = obj.first if obj.is_a?(Array)
|
998
1028
|
obj.send(:update, send(params_name = params_name.to_sym))
|
999
1029
|
end
|
@@ -1018,10 +1048,23 @@ class Object
|
|
1018
1048
|
|
1019
1049
|
def _brick_get_hm_assoc_name(relation, hm_assoc)
|
1020
1050
|
if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
|
1051
|
+
# binding.pry if (same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }) #&&
|
1052
|
+
# x.last[:alternate_name] == hm_assoc[:alternate_name] })
|
1053
|
+
# relation[:fks].any? { |k, v| v[:assoc_name] == new_alt_name }
|
1021
1054
|
plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
|
1022
|
-
|
1055
|
+
# binding.pry if hm_assoc[:assoc_name] == 'issue_issue_duplicates'
|
1056
|
+
new_alt_name = (hm_assoc[:alternate_name] == name.underscore) ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural
|
1057
|
+
# uniq = 1
|
1058
|
+
# while same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }
|
1059
|
+
# hm_assoc[:assoc_name] = "#{hm_assoc_name}_#{uniq += 1}"
|
1060
|
+
# end
|
1061
|
+
# puts new_alt_name
|
1062
|
+
# binding.pry if new_alt_name == 'issue_duplicates'
|
1063
|
+
# hm_assoc[:assoc_name] = new_alt_name
|
1064
|
+
[new_alt_name, true]
|
1023
1065
|
else
|
1024
1066
|
assoc_name = hm_assoc[:inverse_table].pluralize
|
1067
|
+
# hm_assoc[:assoc_name] = assoc_name
|
1025
1068
|
[assoc_name, assoc_name.include?('.')]
|
1026
1069
|
end
|
1027
1070
|
end
|
@@ -1040,11 +1083,20 @@ module ActiveRecord::ConnectionHandling
|
|
1040
1083
|
conn
|
1041
1084
|
end
|
1042
1085
|
|
1086
|
+
# This is done separately so that during testing it can be called right after a migration
|
1087
|
+
# in order to make sure everything is good.
|
1043
1088
|
def _brick_reflect_tables
|
1089
|
+
initializer_loaded = false
|
1044
1090
|
if (relations = ::Brick.relations).empty?
|
1045
|
-
#
|
1091
|
+
# If there's schema things configured then we only expect our initializer to be named exactly this
|
1046
1092
|
if File.exist?(brick_initializer = Rails.root.join('config/initializers/brick.rb'))
|
1047
|
-
load brick_initializer
|
1093
|
+
initializer_loaded = load brick_initializer
|
1094
|
+
end
|
1095
|
+
# Load the initializer for the Apartment gem a little early so that if .excluded_models and
|
1096
|
+
# .default_schema are specified then we can work with non-tenanted models more appropriately
|
1097
|
+
if Object.const_defined?('Apartment') && File.exist?(apartment_initializer = Rails.root.join('config/initializers/apartment.rb'))
|
1098
|
+
load apartment_initializer
|
1099
|
+
apartment_excluded = Apartment.excluded_models
|
1048
1100
|
end
|
1049
1101
|
# Only for Postgres? (Doesn't work in sqlite3)
|
1050
1102
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
@@ -1052,24 +1104,53 @@ module ActiveRecord::ConnectionHandling
|
|
1052
1104
|
schema_sql = 'SELECT NULL AS table_schema;'
|
1053
1105
|
case ActiveRecord::Base.connection.adapter_name
|
1054
1106
|
when 'PostgreSQL'
|
1055
|
-
if (
|
1107
|
+
if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
|
1108
|
+
(sta = multitenancy[:schema_to_analyse]) != 'public')
|
1109
|
+
::Brick.default_schema = schema = sta
|
1056
1110
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1057
1111
|
end
|
1058
1112
|
schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
|
1059
1113
|
when 'Mysql2'
|
1060
1114
|
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
1061
1115
|
when 'SQLite'
|
1116
|
+
# %%% Retrieve internal ActiveRecord table names like this:
|
1117
|
+
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1062
1118
|
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
1063
1119
|
p.name AS column_name, p.type AS data_type,
|
1064
1120
|
CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
|
1065
1121
|
FROM sqlite_master AS m
|
1066
1122
|
INNER JOIN pragma_table_info(m.name) AS p
|
1067
|
-
WHERE m.name NOT IN (
|
1123
|
+
WHERE m.name NOT IN (?, ?)
|
1068
1124
|
ORDER BY m.name, p.cid"
|
1069
1125
|
else
|
1070
1126
|
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
1071
1127
|
end
|
1072
1128
|
|
1129
|
+
unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
|
1130
|
+
db_schemas = db_schemas.to_a
|
1131
|
+
end
|
1132
|
+
unless db_schemas.empty?
|
1133
|
+
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1134
|
+
row = row.is_a?(String) ? row : row['table_schema']
|
1135
|
+
# Remove any system schemas
|
1136
|
+
s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1141
|
+
if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
1142
|
+
if ::Brick.db_schemas.key?(possible_schema)
|
1143
|
+
::Brick.default_schema = schema = possible_schema
|
1144
|
+
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1145
|
+
else
|
1146
|
+
puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\". This schema does not exist. ***"
|
1147
|
+
end
|
1148
|
+
end
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
# %%% Retrieve internal ActiveRecord table names like this:
|
1152
|
+
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1153
|
+
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
1073
1154
|
sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,
|
1074
1155
|
c.column_name, c.data_type,
|
1075
1156
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
@@ -1091,19 +1172,21 @@ module ActiveRecord::ConnectionHandling
|
|
1091
1172
|
WHERE t.table_schema NOT IN ('information_schema', 'pg_catalog')#{"
|
1092
1173
|
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }
|
1093
1174
|
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
1094
|
-
AND t.table_name NOT IN ('pg_stat_statements',
|
1175
|
+
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
1095
1176
|
ORDER BY 1, t.table_type DESC, c.ordinal_position"
|
1096
1177
|
measures = []
|
1097
1178
|
case ActiveRecord::Base.connection.adapter_name
|
1098
1179
|
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1099
|
-
schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1100
|
-
ActiveRecord::Base.execute_sql(sql).each do |r|
|
1101
|
-
#
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1180
|
+
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1181
|
+
ActiveRecord::Base.execute_sql(sql, ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name).each do |r|
|
1182
|
+
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
1183
|
+
# is the default schema, usually 'public'.
|
1184
|
+
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
1185
|
+
Apartment.default_schema if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
|
1186
|
+
elsif ![schema, 'public'].include?(r['schema'])
|
1187
|
+
r['schema']
|
1188
|
+
end
|
1189
|
+
relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
|
1107
1190
|
relation = relations[relation_name]
|
1108
1191
|
relation[:isView] = true if r['table_type'] == 'VIEW'
|
1109
1192
|
col_name = r['column_name']
|
@@ -1122,7 +1205,6 @@ module ActiveRecord::ConnectionHandling
|
|
1122
1205
|
end
|
1123
1206
|
else # MySQL2 acts a little differently, bringing back an array for each row
|
1124
1207
|
ActiveRecord::Base.execute_sql(sql).each do |r|
|
1125
|
-
# next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
|
1126
1208
|
relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
|
1127
1209
|
relation[:isView] = true if r[1] == 'VIEW' # table_type
|
1128
1210
|
col_name = r[2]
|
@@ -1161,7 +1243,7 @@ module ActiveRecord::ConnectionHandling
|
|
1161
1243
|
# end
|
1162
1244
|
# end
|
1163
1245
|
# end
|
1164
|
-
schema = ::Brick.default_schema # Reset back for this next round of fun
|
1246
|
+
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
1165
1247
|
case ActiveRecord::Base.connection.adapter_name
|
1166
1248
|
when 'PostgreSQL', 'Mysql2'
|
1167
1249
|
sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
|
@@ -1176,7 +1258,7 @@ module ActiveRecord::ConnectionHandling
|
|
1176
1258
|
AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
|
1177
1259
|
AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
1178
1260
|
AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
|
1179
|
-
|
1261
|
+
WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
|
1180
1262
|
# AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
|
1181
1263
|
when 'SQLite'
|
1182
1264
|
sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
@@ -1186,36 +1268,47 @@ module ActiveRecord::ConnectionHandling
|
|
1186
1268
|
else
|
1187
1269
|
end
|
1188
1270
|
if sql
|
1189
|
-
::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1190
|
-
unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
|
1191
|
-
db_schemas = db_schemas.to_a
|
1192
|
-
end
|
1193
|
-
unless db_schemas.empty?
|
1194
|
-
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1195
|
-
row = row.is_a?(String) ? row : row['table_schema']
|
1196
|
-
# Remove whatever default schema we're using and other system schemas
|
1197
|
-
s[row] = nil unless ['information_schema', 'pg_catalog', schema].include?(row)
|
1198
|
-
end
|
1199
|
-
end
|
1271
|
+
# ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1200
1272
|
ActiveRecord::Base.execute_sql(sql).each do |fk|
|
1201
1273
|
fk = fk.values unless fk.is_a?(Array)
|
1274
|
+
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
1275
|
+
if apartment_excluded&.include?(fk[1].singularize.camelize)
|
1276
|
+
fk[0] = Apartment.default_schema
|
1277
|
+
elsif fk[0] == 'public' || (is_multitenant && fk[0] == schema)
|
1278
|
+
fk[0] = nil
|
1279
|
+
end
|
1280
|
+
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
1281
|
+
fk[3] = Apartment.default_schema
|
1282
|
+
elsif fk[3] == 'public' || (is_multitenant && fk[3] == schema)
|
1283
|
+
fk[3] = nil
|
1284
|
+
end
|
1202
1285
|
::Brick._add_bt_and_hm(fk, relations)
|
1203
1286
|
end
|
1204
1287
|
end
|
1205
1288
|
end
|
1206
1289
|
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1290
|
+
apartment = Object.const_defined?('Apartment') && Apartment
|
1291
|
+
tables = []
|
1292
|
+
views = []
|
1293
|
+
relations.each do |k, v|
|
1294
|
+
name_parts = k.split('.')
|
1295
|
+
if v.key?(:isView)
|
1296
|
+
views
|
1297
|
+
else
|
1298
|
+
name_parts.shift if apartment && name_parts.length > 1 && name_parts.first == Apartment.default_schema
|
1299
|
+
tables
|
1300
|
+
end << name_parts.map { |x| x.singularize.camelize }.join('::')
|
1301
|
+
end
|
1302
|
+
unless tables.empty?
|
1303
|
+
puts "\nClasses that can be built from tables:"
|
1304
|
+
tables.sort.each { |x| puts x }
|
1305
|
+
end
|
1306
|
+
unless views.empty?
|
1210
1307
|
puts "\nClasses that can be built from views:"
|
1211
|
-
views.
|
1308
|
+
views.sort.each { |x| puts x }
|
1212
1309
|
end
|
1213
1310
|
|
1214
|
-
|
1215
|
-
if File.exist?(brick_initialiser = Rails.root.join('config/initializers/brick.rb'))
|
1216
|
-
load brick_initialiser
|
1217
|
-
::Brick.load_additional_references
|
1218
|
-
end
|
1311
|
+
::Brick.load_additional_references if initializer_loaded
|
1219
1312
|
end
|
1220
1313
|
end
|
1221
1314
|
|
@@ -1242,31 +1335,52 @@ module Brick
|
|
1242
1335
|
# rubocop:enable Style/CommentedKeyword
|
1243
1336
|
|
1244
1337
|
class << self
|
1245
|
-
def _add_bt_and_hm(fk, relations, is_polymorphic = false)
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1338
|
+
def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
|
1339
|
+
bt_assoc_name = fk[2]
|
1340
|
+
unless is_polymorphic
|
1341
|
+
bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
|
1342
|
+
bt_assoc_name[0..-4]
|
1343
|
+
elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_')
|
1344
|
+
bt_assoc_name[0..-3] # Make the bold assumption that we can just peel off any final ID part
|
1345
|
+
else
|
1346
|
+
"#{bt_assoc_name}_bt"
|
1347
|
+
end
|
1252
1348
|
end
|
1253
1349
|
# %%% Temporary schema patch
|
1254
|
-
|
1255
|
-
|
1350
|
+
for_tbl = fk[1]
|
1351
|
+
apartment = Object.const_defined?('Apartment') && Apartment
|
1352
|
+
fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(for_tbl.singularize.camelize)
|
1353
|
+
fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
|
1256
1354
|
bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
|
1355
|
+
|
1257
1356
|
# %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
|
1258
1357
|
# Maybe it's already gotten this info because we got as far as to say there was a unique class
|
1259
1358
|
primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
|
1260
1359
|
pri_tbl = (primary_class = fk[4][:class].constantize).table_name
|
1360
|
+
if (pri_tbl_parts = pri_tbl.split('.')).length > 1
|
1361
|
+
fk[3] = pri_tbl_parts.first
|
1362
|
+
end
|
1261
1363
|
else
|
1364
|
+
is_schema = if ::Brick.config.schema_behavior[:multitenant]
|
1365
|
+
# If Apartment gem lists the primary table as being associated with a non-tenanted model
|
1366
|
+
# then use 'public' schema for the primary table
|
1367
|
+
if apartment&.excluded_models.include?(fk[4].singularize.camelize)
|
1368
|
+
fk[3] = Apartment.default_schema
|
1369
|
+
true
|
1370
|
+
end
|
1371
|
+
else
|
1372
|
+
fk[3] && fk[3] != ::Brick.default_schema && fk[3] != 'public'
|
1373
|
+
end
|
1262
1374
|
pri_tbl = fk[4]
|
1263
|
-
|
1375
|
+
is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
|
1264
1376
|
end
|
1265
1377
|
hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
|
1266
1378
|
|
1267
1379
|
unless (cnstr_name = fk[5])
|
1268
1380
|
# For any appended references (those that come from config), arrive upon a definitely unique constraint name
|
1269
|
-
|
1381
|
+
pri_tbl = is_class ? fk[4][:class].underscore : pri_tbl
|
1382
|
+
pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl.singularize != bt_assoc_name
|
1383
|
+
cnstr_base = cnstr_name = "(brick) #{for_tbl}_#{pri_tbl}"
|
1270
1384
|
cnstr_added_num = 1
|
1271
1385
|
cnstr_name = "#{cnstr_base}_#{cnstr_added_num += 1}" while bts&.key?(cnstr_name) || hms&.key?(cnstr_name)
|
1272
1386
|
missing = []
|
@@ -1306,6 +1420,7 @@ module Brick
|
|
1306
1420
|
else
|
1307
1421
|
inverse_table = [primary_table] if is_polymorphic
|
1308
1422
|
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
|
1423
|
+
assoc_bt[:optional] = true if is_optional
|
1309
1424
|
assoc_bt[:polymorphic] = true if is_polymorphic
|
1310
1425
|
end
|
1311
1426
|
if is_class
|
@@ -1326,7 +1441,14 @@ module Brick
|
|
1326
1441
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
1327
1442
|
assoc_hm[:inverse] = assoc_bt
|
1328
1443
|
else
|
1329
|
-
|
1444
|
+
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == Apartment.default_schema
|
1445
|
+
for_tbl
|
1446
|
+
else
|
1447
|
+
fk[1]
|
1448
|
+
end
|
1449
|
+
# binding.pry if inv_tbl == 'issue_issue_duplicates' # inverse_table goofed?
|
1450
|
+
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: for_tbl.pluralize, alternate_name: bt_assoc_name,
|
1451
|
+
inverse_table: inv_tbl, inverse: assoc_bt }
|
1330
1452
|
assoc_hm[:polymorphic] = true if is_polymorphic
|
1331
1453
|
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
1332
1454
|
hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
|
@@ -124,8 +124,15 @@ module Brick
|
|
124
124
|
schema_options = ::Brick.db_schemas.keys.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
|
125
125
|
# %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
|
126
126
|
# environment or whatever, then get either the controllers or routes list instead
|
127
|
-
|
128
|
-
|
127
|
+
apartment_default_schema = ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && Apartment.default_schema
|
128
|
+
table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).map do |tbl|
|
129
|
+
if (tbl_parts = tbl.split('.')).first == apartment_default_schema
|
130
|
+
tbl = tbl_parts.last
|
131
|
+
end
|
132
|
+
tbl
|
133
|
+
end.sort.each_with_object(+'') do |v, s|
|
134
|
+
s << "<option value=\"#{v.underscore.gsub('.', '/').pluralize}\">#{v}</option>"
|
135
|
+
end.html_safe
|
129
136
|
css = +"<style>
|
130
137
|
#dropper {
|
131
138
|
background-color: #eee;
|
@@ -222,7 +229,7 @@ end %>"
|
|
222
229
|
if ['index', 'show', 'update'].include?(args.first)
|
223
230
|
poly_cols = []
|
224
231
|
css << "<% bts = { #{
|
225
|
-
bts.each_with_object([]) do |v, s|
|
232
|
+
bt_items = bts.each_with_object([]) do |v, s|
|
226
233
|
foreign_models = if v.last[2] # Polymorphic?
|
227
234
|
poly_cols << @_brick_model.reflect_on_association(v[1].first).foreign_type
|
228
235
|
v.last[1].each_with_object([]) { |x, s| s << "[#{x.name}, #{x.primary_key.inspect}]" }.join(', ')
|
@@ -230,14 +237,18 @@ end %>"
|
|
230
237
|
"[#{v.last[1].name}, #{v.last[1].primary_key.inspect}]"
|
231
238
|
end
|
232
239
|
s << "#{v.first.inspect} => [#{v.last.first.inspect}, [#{foreign_models}], #{v.last[2].inspect}]"
|
233
|
-
end
|
240
|
+
end
|
241
|
+
# # %%% Need to fix poly going to an STI class
|
242
|
+
# binding.pry unless poly_cols.empty?
|
243
|
+
bt_items.join(', ')
|
234
244
|
} }
|
235
245
|
poly_cols = #{poly_cols.inspect} %>"
|
236
246
|
end
|
237
247
|
|
238
|
-
# %%% When doing schema select, if
|
248
|
+
# %%% When doing schema select, if we're on a new page go to index
|
239
249
|
script = "<script>
|
240
250
|
var schemaSelect = document.getElementById(\"schema\");
|
251
|
+
var tblSelect = document.getElementById(\"tbl\");
|
241
252
|
var brickSchema;
|
242
253
|
if (schemaSelect) {
|
243
254
|
brickSchema = changeout(location.href, \"_brick_schema\");
|
@@ -247,7 +258,8 @@ if (schemaSelect) {
|
|
247
258
|
schemaSelect.value = brickSchema || \"public\";
|
248
259
|
schemaSelect.focus();
|
249
260
|
schemaSelect.addEventListener(\"change\", function () {
|
250
|
-
|
261
|
+
// If there's an ID then remove it (trim after selected table)
|
262
|
+
location.href = changeout(location.href, \"_brick_schema\", this.value, tblSelect.value);
|
251
263
|
});
|
252
264
|
}
|
253
265
|
[... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
|
@@ -262,7 +274,6 @@ if (schemaSelect) {
|
|
262
274
|
});
|
263
275
|
});
|
264
276
|
|
265
|
-
var tblSelect = document.getElementById(\"tbl\");
|
266
277
|
if (tblSelect) {
|
267
278
|
tblSelect.value = changeout(location.href)[0];
|
268
279
|
if (tblSelect.selectedIndex < 0) tblSelect.value = changeout(location.href)[1];
|
@@ -274,7 +285,7 @@ if (tblSelect) {
|
|
274
285
|
});
|
275
286
|
}
|
276
287
|
|
277
|
-
function changeout(href, param, value) {
|
288
|
+
function changeout(href, param, value, trimAfter) {
|
278
289
|
var hrefParts = href.split(\"?\");
|
279
290
|
if (param === undefined || param === null) {
|
280
291
|
hrefParts = hrefParts[0].split(\"://\");
|
@@ -285,6 +296,11 @@ function changeout(href, param, value) {
|
|
285
296
|
else
|
286
297
|
return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
|
287
298
|
}
|
299
|
+
if (trimAfter) {
|
300
|
+
var pathParts = hrefParts[0].split(\"/\");
|
301
|
+
while (pathParts.lastIndexOf(trimAfter) != pathParts.length - 1) pathParts.pop();
|
302
|
+
hrefParts[0] = pathParts.join(\"/\");
|
303
|
+
}
|
288
304
|
var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
|
289
305
|
params = params.reduce(function (s, v) { var parts = v.split(\"=\"); s[parts[0]] = parts[1]; return s; }, {});
|
290
306
|
if (value === undefined) return params[param];
|
@@ -404,7 +420,7 @@ function changeout(href, param, value) {
|
|
404
420
|
origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
|
405
421
|
# binding.pry
|
406
422
|
if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| puts fk.inspect; fk[:fk] == key_parts.last }) &&
|
407
|
-
|
423
|
+
(obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
|
408
424
|
<h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.name.underscore.tr('/', '_')\}_path\".to_sym, id) %></h3><%
|
409
425
|
end
|
410
426
|
end %>
|
@@ -563,7 +579,7 @@ function changeout(href, param, value) {
|
|
563
579
|
s << "<table id=\"#{hm_name}\">
|
564
580
|
<tr><th>#{hm[3]}</th></tr>
|
565
581
|
<% collection = @#{obj_name}.#{hm_name}
|
566
|
-
collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection]
|
582
|
+
collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection].compact
|
567
583
|
if collection.empty? %>
|
568
584
|
<tr><td>(none)</td></tr>
|
569
585
|
<% else %>
|
@@ -597,6 +613,7 @@ function changeout(href, param, value) {
|
|
597
613
|
|
598
614
|
# Just in case it hadn't been done previously when we tried to load the brick initialiser,
|
599
615
|
# go make sure we've loaded additional references (virtual foreign keys and polymorphic associations).
|
616
|
+
# (This should only happen if for whatever reason the initializer file was not exactly config/initializers/brick.rb.)
|
600
617
|
::Brick.load_additional_references
|
601
618
|
end
|
602
619
|
end
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -93,16 +93,19 @@ module Brick
|
|
93
93
|
attr_accessor :default_schema, :db_schemas
|
94
94
|
|
95
95
|
def set_db_schema(params)
|
96
|
-
schema = params['_brick_schema']
|
96
|
+
schema = params['_brick_schema'] || 'public'
|
97
97
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema) if schema && ::Brick.db_schemas&.include?(schema)
|
98
98
|
end
|
99
99
|
|
100
100
|
# All tables and views (what Postgres calls "relations" including column and foreign key info)
|
101
101
|
def relations
|
102
|
-
connections = Brick.instance_variable_get(:@relations) ||
|
103
|
-
Brick.instance_variable_set(:@relations, (connections = {}))
|
104
102
|
# Key our list of relations for this connection off of the connection pool's object_id
|
105
|
-
(
|
103
|
+
(@relations ||= {})[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } }
|
104
|
+
end
|
105
|
+
|
106
|
+
# If multitenancy is enabled, a list of non-tenanted "global" models
|
107
|
+
def non_tenanted_models
|
108
|
+
@pending_models ||= {}
|
106
109
|
end
|
107
110
|
|
108
111
|
def get_bts_and_hms(model)
|
@@ -129,6 +132,7 @@ module Brick
|
|
129
132
|
associatives = hms.each_with_object({}) do |hmt, s|
|
130
133
|
if (through = hmt.last.options[:through])
|
131
134
|
skip_hms[through] = nil
|
135
|
+
# binding.pry if hmt.first == :issue_issues
|
132
136
|
s[hmt.first] = hms[through] # End up with a hash of HMT names pointing to join-table associations
|
133
137
|
elsif hmt.last.inverse_of.nil?
|
134
138
|
puts "SKIPPING #{hmt.last.name.inspect}"
|
@@ -314,7 +318,7 @@ module Brick
|
|
314
318
|
if ars
|
315
319
|
ars.each do |ar|
|
316
320
|
fk = ar.length < 5 ? [nil, +ar[0], ar[1], nil, +ar[2]] : [ar[0], +ar[1], ar[2], ar[3], +ar[4], ar[5]]
|
317
|
-
::Brick._add_bt_and_hm(fk, relations)
|
321
|
+
::Brick._add_bt_and_hm(fk, relations, false, true)
|
318
322
|
end
|
319
323
|
end
|
320
324
|
if (polys = ::Brick.config.polymorphics)
|
@@ -327,7 +331,7 @@ module Brick
|
|
327
331
|
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'] }
|
328
332
|
v.each do |type|
|
329
333
|
if relations.key?(primary_table = type.underscore.pluralize)
|
330
|
-
::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true)
|
334
|
+
::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true, true)
|
331
335
|
else
|
332
336
|
missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
|
333
337
|
end
|
@@ -406,6 +410,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
406
410
|
::Brick.relations.each do |rel_name, v|
|
407
411
|
rel_name = rel_name.split('.').map(&:underscore)
|
408
412
|
schema_names = rel_name[0..-2]
|
413
|
+
schema_names.shift if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_names.first == Apartment.default_schema
|
409
414
|
k = rel_name.last
|
410
415
|
unless existing_controllers.key?(controller_name = k.pluralize)
|
411
416
|
options = {}
|
@@ -419,6 +424,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
419
424
|
end
|
420
425
|
end
|
421
426
|
end
|
427
|
+
send(:get, '/api-docs/v1/swagger.json', { to: 'brick_swagger#index' }) if Object.const_defined?('Rswag::Ui')
|
422
428
|
end
|
423
429
|
super
|
424
430
|
end
|
@@ -28,7 +28,7 @@ module Brick
|
|
28
28
|
col_down = col.downcase
|
29
29
|
|
30
30
|
if (is_possible_poly = ['character varying', 'text'].include?(type.first))
|
31
|
-
if col_down.end_with?('_type')
|
31
|
+
if col_down.end_with?('_type')
|
32
32
|
poly_type_cut_length = -6
|
33
33
|
col_down = col_down[0..-6]
|
34
34
|
elsif col_down.end_with?('type')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.34
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|