brick 1.0.32 → 1.0.35
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/compatibility.rb +25 -0
- data/lib/brick/extensions.rb +242 -74
- data/lib/brick/frameworks/rails/engine.rb +64 -42
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +5 -22
- data/lib/generators/brick/install_generator.rb +7 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34a23d1057771acea1beb9ea1ea2ff3d27e5983084bcd47f4c87686af77328d5
|
4
|
+
data.tar.gz: 44aea0d5fb92a361913ba4a17ebca62cf5be9aca5585fa9785b7c388c2652cee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9124cbd0db47816db2dcbf59a76adcc0d4dcaea8f4ad02ea9ee99aace025c8914a7871c78a4a703f4daeced7dbaec12d52fdf56b6bc131c4c855be931d4f838
|
7
|
+
data.tar.gz: 671d0333b9a89870db67cdec05306311475724d674aab494c794baa0648736c873ea2aade170bdbc51642b96e4fe76a19f568bf31e724bd513230e1a78c69521
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/version'
|
4
|
+
|
5
|
+
# ActiveRecord before 4.0 didn't have #version
|
6
|
+
unless ActiveRecord.respond_to?(:version)
|
7
|
+
module ActiveRecord
|
8
|
+
def self.version
|
9
|
+
::Gem::Version.new(ActiveRecord::VERSION::STRING)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# In ActiveSupport older than 5.0, the duplicable? test tries to new up a BigDecimal,
|
15
|
+
# and Ruby 2.6 and later deprecates #new. This removes the warning from BigDecimal.
|
16
|
+
# This compatibility needs to be put into place in the application's "config/boot.rb"
|
17
|
+
# file by having the line "require 'brick/compatibility'" to be the last line in that
|
18
|
+
# file.
|
19
|
+
require 'bigdecimal'
|
20
|
+
if ::Gem::Version.new(RUBY_VERSION) >= ::Gem::Version.new('2.6') &&
|
21
|
+
ActiveRecord.version < ::Gem::Version.new('5.0')
|
22
|
+
def BigDecimal.new(*args, **kwargs)
|
23
|
+
BigDecimal(*args, **kwargs)
|
24
|
+
end
|
25
|
+
end
|
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
|
@@ -129,7 +133,7 @@ module ActiveRecord
|
|
129
133
|
end
|
130
134
|
else # With no DSL available, still put this prefix into the JoinArray so we can get primary key (ID) info from this table
|
131
135
|
x = prefix.each_with_object(build_array) { |v, s| s[v.to_sym] }
|
132
|
-
x[prefix
|
136
|
+
x[prefix.last] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
|
133
137
|
end
|
134
138
|
members
|
135
139
|
end
|
@@ -159,6 +163,7 @@ module ActiveRecord
|
|
159
163
|
bracket_name.split('.').each do |part|
|
160
164
|
obj_name += ".#{part}"
|
161
165
|
this_obj = caches.fetch(obj_name) { caches[obj_name] = this_obj&.send(part.to_sym) }
|
166
|
+
break if this_obj.nil?
|
162
167
|
end
|
163
168
|
this_obj&.to_s || ''
|
164
169
|
end
|
@@ -198,7 +203,9 @@ module ActiveRecord
|
|
198
203
|
model_underscore = name.underscore
|
199
204
|
assoc_name = CGI.escapeHTML(assoc_name.to_s)
|
200
205
|
model_path = Rails.application.routes.url_helpers.send("#{model_underscore.tr('/', '_').pluralize}_path".to_sym)
|
201
|
-
|
206
|
+
av_class = Class.new.extend(ActionView::Helpers::UrlHelper)
|
207
|
+
av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('6.1')
|
208
|
+
link = av_class.link_to(name, model_path)
|
202
209
|
model_underscore == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
|
203
210
|
end
|
204
211
|
|
@@ -252,13 +259,12 @@ module ActiveRecord
|
|
252
259
|
names << [piece.right._arel_table_type, (piece.right.table_alias || piece.right.name)]
|
253
260
|
else # "Normal" setup, fed from a JoinSource which has an array of JOINs
|
254
261
|
# The left side is the "JOIN" table
|
255
|
-
names += _recurse_arel(piece.left)
|
262
|
+
names += _recurse_arel(table = piece.left)
|
256
263
|
# The expression on the right side is the "ON" clause
|
257
264
|
# on = piece.right.expr
|
258
265
|
# # Find the table which is not ourselves, and thus must be the "path" that led us here
|
259
266
|
# parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
|
260
267
|
# binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
|
261
|
-
table = piece.left
|
262
268
|
if table.is_a?(Arel::Nodes::TableAlias)
|
263
269
|
alias_name = table.right
|
264
270
|
table = table.left
|
@@ -277,7 +283,8 @@ module ActiveRecord
|
|
277
283
|
@_brick_chains = {}
|
278
284
|
# The left side is the "FROM" table
|
279
285
|
# names += _recurse_arel(piece.left)
|
280
|
-
names << [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)]
|
286
|
+
names << (this_name = [piece.left._arel_table_type, (piece.left.table_alias || piece.left.name)])
|
287
|
+
(_brick_chains[this_name.first] ||= []) << this_name.last
|
281
288
|
# The right side is an array of all JOINs
|
282
289
|
piece.right.each { |join| names << _recurse_arel(join) }
|
283
290
|
end
|
@@ -304,6 +311,23 @@ module ActiveRecord
|
|
304
311
|
# , is_add_bts, is_add_hms
|
305
312
|
)
|
306
313
|
is_add_bts = is_add_hms = true
|
314
|
+
is_distinct = nil
|
315
|
+
wheres = {}
|
316
|
+
params.each do |k, v|
|
317
|
+
case (ks = k.split('.')).length
|
318
|
+
when 1
|
319
|
+
next unless klass._brick_get_fks.include?(k)
|
320
|
+
when 2
|
321
|
+
assoc_name = ks.first.to_sym
|
322
|
+
# Make sure it's a good association name and that the model has that column name
|
323
|
+
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
|
324
|
+
|
325
|
+
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
326
|
+
is_distinct = true
|
327
|
+
distinct!
|
328
|
+
end
|
329
|
+
wheres[k] = v.split(',')
|
330
|
+
end
|
307
331
|
|
308
332
|
# %%% Skip the metadata columns
|
309
333
|
if selects&.empty? # Default to all columns
|
@@ -312,7 +336,9 @@ module ActiveRecord
|
|
312
336
|
if (col_name = col.name) == 'class'
|
313
337
|
col_alias = ' AS _class'
|
314
338
|
end
|
315
|
-
|
339
|
+
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
340
|
+
cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col.name)&.first&.start_with?('xml')
|
341
|
+
selects << "\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
|
316
342
|
end
|
317
343
|
end
|
318
344
|
|
@@ -338,22 +364,6 @@ module ActiveRecord
|
|
338
364
|
end
|
339
365
|
end
|
340
366
|
|
341
|
-
wheres = {}
|
342
|
-
params.each do |k, v|
|
343
|
-
case (ks = k.split('.')).length
|
344
|
-
when 1
|
345
|
-
next unless klass._brick_get_fks.include?(k)
|
346
|
-
when 2
|
347
|
-
assoc_name = ks.first.to_sym
|
348
|
-
# Make sure it's a good association name and that the model has that column name
|
349
|
-
next unless klass.reflect_on_association(assoc_name)&.klass&.column_names&.any?(ks.last)
|
350
|
-
|
351
|
-
join_array[assoc_name] = nil # Store this relation name in our special collection for .joins()
|
352
|
-
distinct!
|
353
|
-
end
|
354
|
-
wheres[k] = v.split(',')
|
355
|
-
end
|
356
|
-
|
357
367
|
if join_array.present?
|
358
368
|
left_outer_joins!(join_array)
|
359
369
|
# Without working from a duplicate, touching the AREL ast tree sets the @arel instance variable, which causes the relation to be immutable.
|
@@ -371,7 +381,9 @@ module ActiveRecord
|
|
371
381
|
v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
|
372
382
|
field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
|
373
383
|
|
374
|
-
|
384
|
+
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
385
|
+
is_xml = is_distinct && Brick.relations[sel_col.first.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
|
386
|
+
selects << "\"#{field_tbl_name}\".\"#{sel_col.last}\"#{'::text' if is_xml} AS \"#{(col_alias = "_brfk_#{v.first}__#{sel_col.last}")}\""
|
375
387
|
v1[idx] << col_alias
|
376
388
|
end
|
377
389
|
|
@@ -489,8 +501,9 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
|
|
489
501
|
alias _brick_autoload_module! autoload_module!
|
490
502
|
def autoload_module!(*args)
|
491
503
|
into, const_name, qualified_name, path_suffix = args
|
492
|
-
base_class_name = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil)
|
493
|
-
|
504
|
+
if (base_class_name = ::Brick.config.sti_namespace_prefixes&.fetch("::#{into.name}::", nil))
|
505
|
+
base_class_name = "::#{base_class_name}" unless base_class_name.start_with?('::')
|
506
|
+
end
|
494
507
|
if (base_class = base_class_name&.constantize)
|
495
508
|
::Brick.sti_models[qualified_name] = { base: base_class }
|
496
509
|
# Build subclass and place it into the specially STI-namespaced module
|
@@ -548,7 +561,7 @@ Module.class_exec do
|
|
548
561
|
full_class_name = +''
|
549
562
|
full_class_name << "::#{self.name}" unless self == Object
|
550
563
|
full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
|
551
|
-
if (model = self.const_get(full_class_name))
|
564
|
+
if (plural_class_name == 'BrickSwagger' || model = self.const_get(full_class_name))
|
552
565
|
# 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.
|
553
566
|
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
554
567
|
end
|
@@ -566,11 +579,13 @@ Module.class_exec do
|
|
566
579
|
[built_module, "module #{schema_name}; end\n"]
|
567
580
|
# # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
|
568
581
|
elsif ::Brick.enable_models?
|
582
|
+
# Custom inheritable Brick base model?
|
583
|
+
class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick')
|
569
584
|
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
570
585
|
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
571
586
|
|
572
|
-
if
|
573
|
-
|
587
|
+
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
588
|
+
self != Object # ... or otherwise already in some namespace?
|
574
589
|
schema_name = [(singular_schema_name = name.underscore),
|
575
590
|
(schema_name = singular_schema_name.pluralize),
|
576
591
|
name,
|
@@ -583,7 +598,7 @@ Module.class_exec do
|
|
583
598
|
if base_model
|
584
599
|
schema_name = name.underscore # For the auto-STI namespace models
|
585
600
|
table_name = base_model.table_name
|
586
|
-
Object.send(:build_model, self, model_name, singular_table_name, table_name, relations, table_name)
|
601
|
+
Object.send(:build_model, self, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
|
587
602
|
else
|
588
603
|
# Adjust for STI if we know of a base model for the requested model name
|
589
604
|
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
@@ -592,10 +607,13 @@ Module.class_exec do
|
|
592
607
|
else
|
593
608
|
ActiveSupport::Inflector.pluralize(singular_table_name)
|
594
609
|
end
|
595
|
-
|
610
|
+
if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') &&
|
611
|
+
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
612
|
+
schema_name = Apartment.default_schema
|
613
|
+
end
|
596
614
|
# Maybe, just maybe there's a database table that will satisfy this need
|
597
615
|
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
598
|
-
Object.send(:build_model, schema_name, model_name, singular_table_name, table_name, relations, matching)
|
616
|
+
Object.send(:build_model, schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
599
617
|
end
|
600
618
|
end
|
601
619
|
end
|
@@ -623,9 +641,12 @@ class Object
|
|
623
641
|
|
624
642
|
private
|
625
643
|
|
626
|
-
def build_model(schema_name, model_name, singular_table_name, table_name, relations, matching)
|
627
|
-
full_name = if schema_name.
|
628
|
-
|
644
|
+
def build_model(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
645
|
+
full_name = if (::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_name == Apartment.default_schema)
|
646
|
+
relation = relations["#{schema_name}.#{matching}"]
|
647
|
+
inheritable_name || model_name
|
648
|
+
elsif schema_name.blank?
|
649
|
+
inheritable_name || model_name
|
629
650
|
else # Prefix the schema to the table name + prefix the schema namespace to the class name
|
630
651
|
schema_module = if schema_name.instance_of?(Module) # from an auto-STI namespace?
|
631
652
|
schema_name
|
@@ -633,21 +654,23 @@ class Object
|
|
633
654
|
matching = "#{schema_name}.#{matching}"
|
634
655
|
(Brick.db_schemas[schema_name] ||= self.const_get(schema_name.singularize.camelize))
|
635
656
|
end
|
636
|
-
"#{schema_module&.name}::#{model_name}"
|
657
|
+
"#{schema_module&.name}::#{inheritable_name || model_name}"
|
637
658
|
end
|
638
659
|
|
639
|
-
return if ((is_view = (relation
|
660
|
+
return if ((is_view = (relation ||= relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
|
640
661
|
::Brick.config.exclude_tables.include?(matching)
|
641
662
|
|
642
663
|
# Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
|
643
664
|
if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
|
644
665
|
unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
|
645
|
-
puts "Warning: Class name for a model that references table \"#{matching
|
666
|
+
puts "Warning: Class name for a model that references table \"#{matching
|
667
|
+
}\" should be \"#{ActiveSupport::Inflector.singularize(inheritable_name || model_name)}\"."
|
646
668
|
end
|
647
669
|
return
|
648
670
|
end
|
649
671
|
|
650
|
-
|
672
|
+
full_model_name = full_name.split('::').tap { |fn| fn[-1] = model_name }.join('::')
|
673
|
+
if (base_model = ::Brick.sti_models[full_model_name]&.fetch(:base, nil) || ::Brick.existing_stis[full_model_name]&.constantize)
|
651
674
|
is_sti = true
|
652
675
|
else
|
653
676
|
base_model = ::Brick.config.models_inherit_from || ActiveRecord::Base
|
@@ -655,9 +678,21 @@ class Object
|
|
655
678
|
hmts = nil
|
656
679
|
code = +"class #{full_name} < #{base_model.name}\n"
|
657
680
|
built_model = Class.new(base_model) do |new_model_class|
|
658
|
-
(schema_module || Object).const_set(model_name.to_sym, new_model_class)
|
681
|
+
(schema_module || Object).const_set((inheritable_name || model_name).to_sym, new_model_class)
|
682
|
+
if inheritable_name
|
683
|
+
new_model_class.define_singleton_method :inherited do |subclass|
|
684
|
+
super(subclass)
|
685
|
+
if subclass.name == model_name
|
686
|
+
puts "#{full_model_name} properly extends from #{full_name}"
|
687
|
+
else
|
688
|
+
puts "should be \"class #{model_name} < #{inheritable_name}\"\n (not \"#{subclass.name} < #{inheritable_name}\")"
|
689
|
+
end
|
690
|
+
end
|
691
|
+
self.abstract_class = true
|
692
|
+
code << " self.abstract_class = true\n"
|
693
|
+
end
|
659
694
|
# Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
|
660
|
-
code << " self.table_name = '#{self.table_name = matching}'\n"
|
695
|
+
code << " self.table_name = '#{self.table_name = matching}'\n" if inheritable_name || table_name != matching
|
661
696
|
|
662
697
|
# Override models backed by a view so they return true for #is_view?
|
663
698
|
# (Dynamically-created controllers and view templates for such models will then act in a read-only way)
|
@@ -685,6 +720,9 @@ class Object
|
|
685
720
|
code << " self.primary_key = #{pk_sym.inspect}\n"
|
686
721
|
end
|
687
722
|
_brick_primary_key(relation) # Set the newly-found PK in the instance variable
|
723
|
+
elsif (possible_pk = ActiveRecord::Base.get_primary_key(base_class.name)) && relation[:cols][possible_pk]
|
724
|
+
new_model_class.primary_key = (possible_pk = possible_pk.to_sym)
|
725
|
+
code << " self.primary_key = #{possible_pk.inspect}\n"
|
688
726
|
else
|
689
727
|
code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
|
690
728
|
end
|
@@ -725,7 +763,8 @@ class Object
|
|
725
763
|
hmts&.each do |hmt_fk, fks|
|
726
764
|
hmt_fk = hmt_fk.tr('.', '_')
|
727
765
|
fks.each do |fk|
|
728
|
-
|
766
|
+
# %%% Will not work with custom has_many name
|
767
|
+
through = ::Brick.config.schema_behavior[:multitenant] ? fk.first[:assoc_name] : fk.first[:inverse_table].tr('.', '_').pluralize
|
729
768
|
hmt_name = if fks.length > 1
|
730
769
|
if fks[0].first[:inverse][:assoc_name] == fks[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
|
731
770
|
"#{hmt_fk}_through_#{fk.first[:assoc_name]}"
|
@@ -740,8 +779,12 @@ class Object
|
|
740
779
|
options = { through: through.to_sym }
|
741
780
|
if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
|
742
781
|
hmt_name = "#{hmt_name.singularize}_#{fk.first[:assoc_name]}"
|
743
|
-
|
744
|
-
options[:
|
782
|
+
# Was:
|
783
|
+
# options[:class_name] = fk.first[:inverse_table].singularize.camelize
|
784
|
+
# options[:foreign_key] = fk.first[:fk].to_sym
|
785
|
+
far_assoc = relations[fk.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == fk.last }
|
786
|
+
options[:class_name] = far_assoc.last[:inverse_table].singularize.camelize
|
787
|
+
options[:foreign_key] = far_assoc.last[:fk].to_sym
|
745
788
|
end
|
746
789
|
options[:source] = fk.last.to_sym unless hmt_name.singularize == fk.last
|
747
790
|
code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
|
@@ -843,19 +886,70 @@ class Object
|
|
843
886
|
def build_controller(namespace, class_name, plural_class_name, model, relations)
|
844
887
|
table_name = ActiveSupport::Inflector.underscore(plural_class_name)
|
845
888
|
singular_table_name = ActiveSupport::Inflector.singularize(table_name)
|
846
|
-
pk = model
|
889
|
+
pk = model&._brick_primary_key(relations.fetch(table_name, nil))
|
847
890
|
|
848
891
|
namespace_name = "#{namespace.name}::" if namespace
|
849
892
|
code = +"class #{namespace_name}#{class_name} < ApplicationController\n"
|
850
893
|
built_controller = Class.new(ActionController::Base) do |new_controller_class|
|
851
894
|
(namespace || Object).const_set(class_name.to_sym, new_controller_class)
|
852
895
|
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
896
|
+
unless (is_swagger = plural_class_name == 'BrickSwagger') # && request.format == :json)
|
897
|
+
code << " def index\n"
|
898
|
+
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{pk.inspect})" : '.all'}\n"
|
899
|
+
code << " @#{table_name}.brick_select(params)\n"
|
900
|
+
code << " end\n"
|
901
|
+
end
|
857
902
|
self.protect_from_forgery unless: -> { self.request.format.js? }
|
858
903
|
self.define_method :index do
|
904
|
+
if is_swagger
|
905
|
+
json = { 'openapi': '3.0.1', 'info': { 'title': 'API V1', 'version': 'v1' },
|
906
|
+
'servers': [
|
907
|
+
{ 'url': 'https://{defaultHost}', 'variables': { 'defaultHost': { 'default': 'www.example.com' } } }
|
908
|
+
]
|
909
|
+
}
|
910
|
+
json['paths'] = relations.inject({}) do |s, v|
|
911
|
+
s["/api/v1/#{v.first}"] = {
|
912
|
+
'get': {
|
913
|
+
'summary': "list #{v.first}",
|
914
|
+
'parameters': v.last[:cols].map { |k, v| { 'name' => k, 'schema': { 'type': v.first } } },
|
915
|
+
'responses': { '200': { 'description': 'successful' } }
|
916
|
+
}
|
917
|
+
}
|
918
|
+
# next if v.last[:isView]
|
919
|
+
|
920
|
+
s["/api/v1/#{v.first}/{id}"] = {
|
921
|
+
'patch': {
|
922
|
+
'summary': "update a #{v.first.singularize}",
|
923
|
+
'parameters': v.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
|
924
|
+
{ 'name' => k, 'schema': { 'type': v.first } }
|
925
|
+
end,
|
926
|
+
'responses': { '200': { 'description': 'successful' } }
|
927
|
+
}
|
928
|
+
# "/api/v1/books/{id}": {
|
929
|
+
# "parameters": [
|
930
|
+
# {
|
931
|
+
# "name": "id",
|
932
|
+
# "in": "path",
|
933
|
+
# "description": "id",
|
934
|
+
# "required": true,
|
935
|
+
# "schema": {
|
936
|
+
# "type": "string"
|
937
|
+
# }
|
938
|
+
# },
|
939
|
+
# {
|
940
|
+
# "name": "Authorization",
|
941
|
+
# "in": "header",
|
942
|
+
# "schema": {
|
943
|
+
# "type": "string"
|
944
|
+
# }
|
945
|
+
# }
|
946
|
+
# ],
|
947
|
+
}
|
948
|
+
s
|
949
|
+
end
|
950
|
+
render inline: json.to_json, content_type: request.format
|
951
|
+
return
|
952
|
+
end
|
859
953
|
::Brick.set_db_schema(params)
|
860
954
|
if request.format == :csv # Asking for a template?
|
861
955
|
require 'csv'
|
@@ -869,7 +963,8 @@ class Object
|
|
869
963
|
return
|
870
964
|
end
|
871
965
|
|
872
|
-
|
966
|
+
quoted_table_name = model.table_name.split('.').map { |x| "\"#{x}\"" }.join('.')
|
967
|
+
order = pk.each_with_object([]) { |pk_part, s| s << "#{quoted_table_name}.\"#{pk_part}\"" }
|
873
968
|
ar_relation = order.present? ? model.order("#{order.join(', ')}") : model.all
|
874
969
|
@_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
|
875
970
|
# %%% Add custom HM count columns
|
@@ -884,7 +979,8 @@ class Object
|
|
884
979
|
@_brick_join_array = join_array
|
885
980
|
end
|
886
981
|
|
887
|
-
|
982
|
+
is_pk_string = nil
|
983
|
+
if (pk_col = model&.primary_key)
|
888
984
|
code << " def show\n"
|
889
985
|
code << (find_by_id = " id = params[:id]&.split(/[\\/,_]/)
|
890
986
|
id = id.first if id.is_a?(Array) && id.length == 1
|
@@ -892,14 +988,19 @@ class Object
|
|
892
988
|
code << " end\n"
|
893
989
|
self.define_method :show do
|
894
990
|
::Brick.set_db_schema(params)
|
895
|
-
id =
|
991
|
+
id = if model.columns_hash[pk_col]&.type == :string
|
992
|
+
is_pk_string = true
|
993
|
+
params[:id]
|
994
|
+
else
|
995
|
+
params[:id]&.split(/[\/,_]/)
|
996
|
+
end
|
896
997
|
id = id.first if id.is_a?(Array) && id.length == 1
|
897
998
|
instance_variable_set("@#{singular_table_name}".to_sym, model.find(id))
|
898
999
|
end
|
899
1000
|
end
|
900
1001
|
|
901
1002
|
# By default, views get marked as read-only
|
902
|
-
unless
|
1003
|
+
unless is_swagger # model.readonly # (relation = relations[model.table_name]).key?(:isView)
|
903
1004
|
code << " # (Define :new, :create)\n"
|
904
1005
|
|
905
1006
|
if model.primary_key
|
@@ -931,7 +1032,7 @@ class Object
|
|
931
1032
|
# return
|
932
1033
|
end
|
933
1034
|
|
934
|
-
id = params[:id]&.split(/[\/,_]/)
|
1035
|
+
id = is_pk_string ? params[:id] : params[:id]&.split(/[\/,_]/)
|
935
1036
|
id = id.first if id.is_a?(Array) && id.length == 1
|
936
1037
|
instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(id)))
|
937
1038
|
obj = obj.first if obj.is_a?(Array)
|
@@ -957,11 +1058,20 @@ class Object
|
|
957
1058
|
end
|
958
1059
|
|
959
1060
|
def _brick_get_hm_assoc_name(relation, hm_assoc)
|
960
|
-
if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
|
1061
|
+
if (relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1) &&
|
1062
|
+
hm_assoc[:alternate_name] != hm_assoc[:inverse][:assoc_name]
|
961
1063
|
plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
|
962
|
-
|
1064
|
+
new_alt_name = (hm_assoc[:alternate_name] == name.underscore) ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural
|
1065
|
+
# uniq = 1
|
1066
|
+
# while same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }
|
1067
|
+
# hm_assoc[:assoc_name] = "#{hm_assoc_name}_#{uniq += 1}"
|
1068
|
+
# end
|
1069
|
+
# puts new_alt_name
|
1070
|
+
# hm_assoc[:assoc_name] = new_alt_name
|
1071
|
+
[new_alt_name, true]
|
963
1072
|
else
|
964
1073
|
assoc_name = hm_assoc[:inverse_table].pluralize
|
1074
|
+
# hm_assoc[:assoc_name] = assoc_name
|
965
1075
|
[assoc_name, assoc_name.include?('.')]
|
966
1076
|
end
|
967
1077
|
end
|
@@ -989,6 +1099,12 @@ module ActiveRecord::ConnectionHandling
|
|
989
1099
|
if File.exist?(brick_initializer = Rails.root.join('config/initializers/brick.rb'))
|
990
1100
|
initializer_loaded = load brick_initializer
|
991
1101
|
end
|
1102
|
+
# Load the initializer for the Apartment gem a little early so that if .excluded_models and
|
1103
|
+
# .default_schema are specified then we can work with non-tenanted models more appropriately
|
1104
|
+
if Object.const_defined?('Apartment') && File.exist?(apartment_initializer = Rails.root.join('config/initializers/apartment.rb'))
|
1105
|
+
load apartment_initializer
|
1106
|
+
apartment_excluded = Apartment.excluded_models
|
1107
|
+
end
|
992
1108
|
# Only for Postgres? (Doesn't work in sqlite3)
|
993
1109
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
994
1110
|
|
@@ -1004,12 +1120,14 @@ module ActiveRecord::ConnectionHandling
|
|
1004
1120
|
when 'Mysql2'
|
1005
1121
|
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
1006
1122
|
when 'SQLite'
|
1123
|
+
# %%% Retrieve internal ActiveRecord table names like this:
|
1124
|
+
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1007
1125
|
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
1008
1126
|
p.name AS column_name, p.type AS data_type,
|
1009
1127
|
CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
|
1010
1128
|
FROM sqlite_master AS m
|
1011
1129
|
INNER JOIN pragma_table_info(m.name) AS p
|
1012
|
-
WHERE m.name NOT IN (
|
1130
|
+
WHERE m.name NOT IN (?, ?)
|
1013
1131
|
ORDER BY m.name, p.cid"
|
1014
1132
|
else
|
1015
1133
|
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
@@ -1037,6 +1155,9 @@ module ActiveRecord::ConnectionHandling
|
|
1037
1155
|
end
|
1038
1156
|
end
|
1039
1157
|
|
1158
|
+
# %%% Retrieve internal ActiveRecord table names like this:
|
1159
|
+
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1160
|
+
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
1040
1161
|
sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,
|
1041
1162
|
c.column_name, c.data_type,
|
1042
1163
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
@@ -1058,18 +1179,22 @@ module ActiveRecord::ConnectionHandling
|
|
1058
1179
|
WHERE t.table_schema NOT IN ('information_schema', 'pg_catalog')#{"
|
1059
1180
|
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }
|
1060
1181
|
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
1061
|
-
AND t.table_name NOT IN ('pg_stat_statements',
|
1182
|
+
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
1062
1183
|
ORDER BY 1, t.table_type DESC, c.ordinal_position"
|
1063
1184
|
measures = []
|
1064
1185
|
case ActiveRecord::Base.connection.adapter_name
|
1065
1186
|
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1066
1187
|
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1067
|
-
ActiveRecord::
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1188
|
+
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
|
1189
|
+
ActiveRecord::Base.execute_sql(sql, ActiveRecord::Base.schema_migrations_table_name, ar_imtn).each do |r|
|
1190
|
+
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
1191
|
+
# is the default schema, usually 'public'.
|
1192
|
+
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
1193
|
+
Apartment.default_schema if apartment_excluded&.include?(r['relation_name'].singularize.camelize)
|
1194
|
+
elsif ![schema, 'public'].include?(r['schema'])
|
1195
|
+
r['schema']
|
1196
|
+
end
|
1197
|
+
relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
|
1073
1198
|
relation = relations[relation_name]
|
1074
1199
|
relation[:isView] = true if r['table_type'] == 'VIEW'
|
1075
1200
|
col_name = r['column_name']
|
@@ -1154,19 +1279,41 @@ module ActiveRecord::ConnectionHandling
|
|
1154
1279
|
# ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1155
1280
|
ActiveRecord::Base.execute_sql(sql).each do |fk|
|
1156
1281
|
fk = fk.values unless fk.is_a?(Array)
|
1157
|
-
# Multitenancy makes things a little more general overall
|
1158
|
-
|
1159
|
-
|
1282
|
+
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
1283
|
+
if apartment_excluded&.include?(fk[1].singularize.camelize)
|
1284
|
+
fk[0] = Apartment.default_schema
|
1285
|
+
elsif fk[0] == 'public' || (is_multitenant && fk[0] == schema)
|
1286
|
+
fk[0] = nil
|
1287
|
+
end
|
1288
|
+
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
1289
|
+
fk[3] = Apartment.default_schema
|
1290
|
+
elsif fk[3] == 'public' || (is_multitenant && fk[3] == schema)
|
1291
|
+
fk[3] = nil
|
1292
|
+
end
|
1160
1293
|
::Brick._add_bt_and_hm(fk, relations)
|
1161
1294
|
end
|
1162
1295
|
end
|
1163
1296
|
end
|
1164
1297
|
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1298
|
+
apartment = Object.const_defined?('Apartment') && Apartment
|
1299
|
+
tables = []
|
1300
|
+
views = []
|
1301
|
+
relations.each do |k, v|
|
1302
|
+
name_parts = k.split('.')
|
1303
|
+
if v.key?(:isView)
|
1304
|
+
views
|
1305
|
+
else
|
1306
|
+
name_parts.shift if apartment && name_parts.length > 1 && name_parts.first == Apartment.default_schema
|
1307
|
+
tables
|
1308
|
+
end << name_parts.map { |x| x.singularize.camelize }.join('::')
|
1309
|
+
end
|
1310
|
+
unless tables.empty?
|
1311
|
+
puts "\nClasses that can be built from tables:"
|
1312
|
+
tables.sort.each { |x| puts x }
|
1313
|
+
end
|
1314
|
+
unless views.empty?
|
1168
1315
|
puts "\nClasses that can be built from views:"
|
1169
|
-
views.
|
1316
|
+
views.sort.each { |x| puts x }
|
1170
1317
|
end
|
1171
1318
|
|
1172
1319
|
::Brick.load_additional_references if initializer_loaded
|
@@ -1209,16 +1356,31 @@ module Brick
|
|
1209
1356
|
end
|
1210
1357
|
# %%% Temporary schema patch
|
1211
1358
|
for_tbl = fk[1]
|
1359
|
+
apartment = Object.const_defined?('Apartment') && Apartment
|
1360
|
+
fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(for_tbl.singularize.camelize)
|
1212
1361
|
fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
|
1213
1362
|
bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
|
1363
|
+
|
1214
1364
|
# %%% Do we miss out on has_many :through or even HM based on constantizing this model early?
|
1215
1365
|
# Maybe it's already gotten this info because we got as far as to say there was a unique class
|
1216
1366
|
primary_table = if (is_class = fk[4].is_a?(Hash) && fk[4].key?(:class))
|
1217
1367
|
pri_tbl = (primary_class = fk[4][:class].constantize).table_name
|
1368
|
+
if (pri_tbl_parts = pri_tbl.split('.')).length > 1
|
1369
|
+
fk[3] = pri_tbl_parts.first
|
1370
|
+
end
|
1218
1371
|
else
|
1219
|
-
is_schema =
|
1372
|
+
is_schema = if ::Brick.config.schema_behavior[:multitenant]
|
1373
|
+
# If Apartment gem lists the primary table as being associated with a non-tenanted model
|
1374
|
+
# then use 'public' schema for the primary table
|
1375
|
+
if apartment && apartment&.excluded_models.include?(fk[4].singularize.camelize)
|
1376
|
+
fk[3] = Apartment.default_schema
|
1377
|
+
true
|
1378
|
+
end
|
1379
|
+
else
|
1380
|
+
fk[3] && fk[3] != ::Brick.default_schema && fk[3] != 'public'
|
1381
|
+
end
|
1220
1382
|
pri_tbl = fk[4]
|
1221
|
-
|
1383
|
+
is_schema ? "#{fk[3]}.#{pri_tbl}" : pri_tbl
|
1222
1384
|
end
|
1223
1385
|
hms = (relation = relations.fetch(primary_table, nil))&.fetch(:fks) { relation[:fks] = {} } unless is_class
|
1224
1386
|
|
@@ -1287,7 +1449,13 @@ module Brick
|
|
1287
1449
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
1288
1450
|
assoc_hm[:inverse] = assoc_bt
|
1289
1451
|
else
|
1290
|
-
|
1452
|
+
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == Apartment.default_schema
|
1453
|
+
for_tbl
|
1454
|
+
else
|
1455
|
+
fk[1]
|
1456
|
+
end
|
1457
|
+
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: for_tbl.pluralize, alternate_name: bt_assoc_name,
|
1458
|
+
inverse_table: inv_tbl, inverse: assoc_bt }
|
1291
1459
|
assoc_hm[:polymorphic] = true if is_polymorphic
|
1292
1460
|
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
1293
1461
|
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;
|
@@ -209,12 +216,17 @@ input[type=submit] {
|
|
209
216
|
<% def is_bcrypt?(val)
|
210
217
|
val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
|
211
218
|
end
|
212
|
-
def hide_bcrypt(val)
|
219
|
+
def hide_bcrypt(val, max_len = 200)
|
213
220
|
if is_bcrypt?(val)
|
214
221
|
'(hidden)'
|
215
|
-
elsif val.is_a?(String) && val.encoding.name != 'UTF-8'
|
216
|
-
val[0..1000].force_encoding('UTF-8')
|
217
222
|
else
|
223
|
+
if val.is_a?(String)
|
224
|
+
if val.length > max_len
|
225
|
+
val = val[0...max_len]
|
226
|
+
val << '...'
|
227
|
+
end
|
228
|
+
val.force_encoding('UTF-8') unless val.encoding.name == 'UTF-8'
|
229
|
+
end
|
218
230
|
val
|
219
231
|
end
|
220
232
|
end %>"
|
@@ -238,46 +250,51 @@ end %>"
|
|
238
250
|
poly_cols = #{poly_cols.inspect} %>"
|
239
251
|
end
|
240
252
|
|
241
|
-
# %%% When doing schema select, if
|
253
|
+
# %%% When doing schema select, if we're on a new page go to index
|
242
254
|
script = "<script>
|
243
255
|
var schemaSelect = document.getElementById(\"schema\");
|
256
|
+
var tblSelect = document.getElementById(\"tbl\");
|
244
257
|
var brickSchema;
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
258
|
+
|
259
|
+
// This PageTransitionEvent fires when the page first loads, as well as after any other history
|
260
|
+
// transition such as when using the browser's Back and Forward buttons.
|
261
|
+
window.addEventListener(\"pageshow\", function() {
|
262
|
+
if (schemaSelect) { // First drop-down is only present if multitenant
|
263
|
+
brickSchema = changeout(location.href, \"_brick_schema\");
|
264
|
+
if (brickSchema) {
|
265
|
+
[... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
|
266
|
+
}
|
267
|
+
schemaSelect.value = brickSchema || \"public\";
|
268
|
+
schemaSelect.focus();
|
269
|
+
schemaSelect.addEventListener(\"change\", function () {
|
270
|
+
// If there's an ID then remove it (trim after selected table)
|
271
|
+
location.href = changeout(location.href, \"_brick_schema\", this.value, tblSelect.value);
|
272
|
+
});
|
249
273
|
}
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
form.addEventListener('submit', function (ev) {
|
260
|
-
[... ev.target.getElementsByTagName(\"SELECT\")].forEach(function (select) {
|
261
|
-
if (select.value === \"^^^brick_NULL^^^\")
|
262
|
-
select.value = null;
|
274
|
+
[... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
|
275
|
+
if (brickSchema)
|
276
|
+
form.action = changeout(form.action, \"_brick_schema\", brickSchema);
|
277
|
+
form.addEventListener('submit', function (ev) {
|
278
|
+
[... ev.target.getElementsByTagName(\"SELECT\")].forEach(function (select) {
|
279
|
+
if (select.value === \"^^^brick_NULL^^^\")
|
280
|
+
select.value = null;
|
281
|
+
});
|
282
|
+
return true;
|
263
283
|
});
|
264
|
-
return true;
|
265
284
|
});
|
266
|
-
});
|
267
285
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
}
|
286
|
+
if (tblSelect) { // Always present
|
287
|
+
tblSelect.value = changeout(location.href)[schemaSelect ? 1 : 0];
|
288
|
+
tblSelect.addEventListener(\"change\", function () {
|
289
|
+
var lhr = changeout(location.href, null, this.value);
|
290
|
+
if (brickSchema)
|
291
|
+
lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
|
292
|
+
location.href = lhr;
|
293
|
+
});
|
294
|
+
}
|
295
|
+
});
|
279
296
|
|
280
|
-
function changeout(href, param, value) {
|
297
|
+
function changeout(href, param, value, trimAfter) {
|
281
298
|
var hrefParts = href.split(\"?\");
|
282
299
|
if (param === undefined || param === null) {
|
283
300
|
hrefParts = hrefParts[0].split(\"://\");
|
@@ -288,6 +305,11 @@ function changeout(href, param, value) {
|
|
288
305
|
else
|
289
306
|
return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
|
290
307
|
}
|
308
|
+
if (trimAfter) {
|
309
|
+
var pathParts = hrefParts[0].split(\"/\");
|
310
|
+
while (pathParts.lastIndexOf(trimAfter) != pathParts.length - 1) pathParts.pop();
|
311
|
+
hrefParts[0] = pathParts.join(\"/\");
|
312
|
+
}
|
291
313
|
var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
|
292
314
|
params = params.reduce(function (s, v) { var parts = v.split(\"=\"); s[parts[0]] = parts[1]; return s; }, {});
|
293
315
|
if (value === undefined) return params[param];
|
@@ -353,7 +375,6 @@ function changeout(href, param, value) {
|
|
353
375
|
|
354
376
|
async function updateSignInStatus(isSignedIn) {
|
355
377
|
if (isSignedIn) {
|
356
|
-
console.log(\"turds!\");
|
357
378
|
await gapi.client.sheets.spreadsheets.create({
|
358
379
|
properties: {
|
359
380
|
title: #{table_name.inspect},
|
@@ -407,7 +428,7 @@ function changeout(href, param, value) {
|
|
407
428
|
origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
|
408
429
|
# binding.pry
|
409
430
|
if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| puts fk.inspect; fk[:fk] == key_parts.last }) &&
|
410
|
-
|
431
|
+
(obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
|
411
432
|
<h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.name.underscore.tr('/', '_')\}_path\".to_sym, id) %></h3><%
|
412
433
|
end
|
413
434
|
end %>
|
@@ -442,7 +463,6 @@ function changeout(href, param, value) {
|
|
442
463
|
::Brick.config.metadata_columns.include?(k) || poly_cols.include?(k) || k.start_with?('_brfk_') || (k.start_with?('_br_') && (k.length == 63 || k.end_with?('_ct'))) %>
|
443
464
|
<td>
|
444
465
|
<% if (bt = bts[k]) %>
|
445
|
-
<%# binding.pry # Postgres column names are limited to 63 characters %>
|
446
466
|
<% if bt[2] # Polymorphic?
|
447
467
|
bt_class = #{obj_name}.send(\"#\{bt.first\}_type\")
|
448
468
|
base_class = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class.name.underscore
|
@@ -451,6 +471,7 @@ function changeout(href, param, value) {
|
|
451
471
|
send(\"#\{base_class\}_path\".to_sym, poly_id)) if poly_id %><%
|
452
472
|
else
|
453
473
|
bt_txt = (bt_class = bt[1].first.first).brick_descrip(
|
474
|
+
# 0..62 because Postgres column names are limited to 63 characters
|
454
475
|
#{obj_name}, (descrips = @_brick_bt_descrip[bt.first][bt_class])[0..-2].map { |z| #{obj_name}.send(z.last[0..62]) }, (bt_id_col = descrips.last)
|
455
476
|
)
|
456
477
|
bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
|
@@ -533,7 +554,7 @@ function changeout(href, param, value) {
|
|
533
554
|
<% else case #{model_name}.column_for_attribute(k).type
|
534
555
|
when :string, :text %>
|
535
556
|
<% if is_bcrypt?(val) # || .readonly? %>
|
536
|
-
<%= hide_bcrypt(val) %>
|
557
|
+
<%= hide_bcrypt(val, 1000) %>
|
537
558
|
<% else %>
|
538
559
|
<div class=\"wide-input\"><%= f.text_field k.to_sym %></div>
|
539
560
|
<% end %>
|
@@ -566,7 +587,7 @@ function changeout(href, param, value) {
|
|
566
587
|
s << "<table id=\"#{hm_name}\">
|
567
588
|
<tr><th>#{hm[3]}</th></tr>
|
568
589
|
<% collection = @#{obj_name}.#{hm_name}
|
569
|
-
collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection]
|
590
|
+
collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection].compact
|
570
591
|
if collection.empty? %>
|
571
592
|
<tr><td>(none)</td></tr>
|
572
593
|
<% else %>
|
@@ -600,6 +621,7 @@ function changeout(href, param, value) {
|
|
600
621
|
|
601
622
|
# Just in case it hadn't been done previously when we tried to load the brick initialiser,
|
602
623
|
# go make sure we've loaded additional references (virtual foreign keys and polymorphic associations).
|
624
|
+
# (This should only happen if for whatever reason the initializer file was not exactly config/initializers/brick.rb.)
|
603
625
|
::Brick.load_additional_references
|
604
626
|
end
|
605
627
|
end
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -1,25 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
|
5
|
-
# ActiveRecord before 4.0 didn't have #version
|
6
|
-
unless ActiveRecord.respond_to?(:version)
|
7
|
-
module ActiveRecord
|
8
|
-
def self.version
|
9
|
-
::Gem::Version.new(ActiveRecord::VERSION::STRING)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
# In ActiveSupport older than 5.0, the duplicable? test tries to new up a BigDecimal,
|
15
|
-
# and Ruby 2.6 and later deprecates #new. This removes the warning from BigDecimal.
|
16
|
-
require 'bigdecimal'
|
17
|
-
if (ruby_version = ::Gem::Version.new(RUBY_VERSION)) >= ::Gem::Version.new('2.6') &&
|
18
|
-
ActiveRecord.version < ::Gem::Version.new('5.0')
|
19
|
-
def BigDecimal.new(*args, **kwargs)
|
20
|
-
BigDecimal(*args, **kwargs)
|
21
|
-
end
|
22
|
-
end
|
3
|
+
require 'brick/compatibility'
|
23
4
|
|
24
5
|
# Allow ActiveRecord 4.0 and 4.1 to work with newer Ruby (>= 2.4) by avoiding a "stack level too deep"
|
25
6
|
# error when ActiveSupport tries to smarten up Numeric by messing with Fixnum and Bignum at the end of:
|
@@ -45,8 +26,8 @@ end
|
|
45
26
|
require 'brick/util'
|
46
27
|
|
47
28
|
# Allow ActiveRecord < 3.2 to work with Ruby 2.7 and later
|
48
|
-
if
|
49
|
-
|
29
|
+
if (ruby_version = ::Gem::Version.new(RUBY_VERSION)) >= ::Gem::Version.new('2.7') &&
|
30
|
+
ActiveRecord.version < ::Gem::Version.new('3.2')
|
50
31
|
# Remove circular reference for "now"
|
51
32
|
::Brick::Util._patch_require(
|
52
33
|
'active_support/values/time_zone.rb', '/activesupport',
|
@@ -409,6 +390,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
409
390
|
::Brick.relations.each do |rel_name, v|
|
410
391
|
rel_name = rel_name.split('.').map(&:underscore)
|
411
392
|
schema_names = rel_name[0..-2]
|
393
|
+
schema_names.shift if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && schema_names.first == Apartment.default_schema
|
412
394
|
k = rel_name.last
|
413
395
|
unless existing_controllers.key?(controller_name = k.pluralize)
|
414
396
|
options = {}
|
@@ -422,6 +404,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
422
404
|
end
|
423
405
|
end
|
424
406
|
end
|
407
|
+
send(:get, '/api-docs/v1/swagger.json', { to: 'brick_swagger#index' }) if Object.const_defined?('Rswag::Ui')
|
425
408
|
end
|
426
409
|
super
|
427
410
|
end
|
@@ -18,7 +18,8 @@ module Brick
|
|
18
18
|
desc 'Generates an initializer file for configuring Brick'
|
19
19
|
|
20
20
|
def create_initializer_file
|
21
|
-
|
21
|
+
is_brick_file = File.exist?(filename = 'config/initializers/brick.rb')
|
22
|
+
if is_brick_file && ::Brick.config.schema_behavior[:multitenant] || !is_brick_file
|
22
23
|
# See if we can make suggestions for additional_references and polymorphic associations
|
23
24
|
resembles_fks = Hash.new { |h, k| h[k] = [] }
|
24
25
|
possible_polymorphics = {}
|
@@ -28,7 +29,7 @@ module Brick
|
|
28
29
|
col_down = col.downcase
|
29
30
|
|
30
31
|
if (is_possible_poly = ['character varying', 'text'].include?(type.first))
|
31
|
-
if col_down.end_with?('_type')
|
32
|
+
if col_down.end_with?('_type')
|
32
33
|
poly_type_cut_length = -6
|
33
34
|
col_down = col_down[0..-6]
|
34
35
|
elsif col_down.end_with?('type')
|
@@ -223,7 +224,10 @@ module Brick
|
|
223
224
|
# # Database schema to use when analysing existing data, such as deriving a list of polymorphic classes in the case that
|
224
225
|
# # it wasn't originally specified.
|
225
226
|
# Brick.schema_behavior = :namespaced
|
226
|
-
# Brick.schema_behavior = { multitenant: { schema_to_analyse:
|
227
|
+
#{Brick.config.schema_behavior ? "Brick.schema_behavior = { multitenant: { schema_to_analyse: #{
|
228
|
+
Brick.config.schema_behavior[:multitenant][:schema_to_analyse].inspect}" :
|
229
|
+
"# Brick.schema_behavior = { multitenant: { schema_to_analyse: 'engineering'"
|
230
|
+
} } }
|
227
231
|
|
228
232
|
# # Polymorphic associations are set up by providing a model name and polymorphic association name#{poly}
|
229
233
|
|
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.35
|
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-06-
|
11
|
+
date: 2022-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -214,6 +214,7 @@ extensions: []
|
|
214
214
|
extra_rdoc_files: []
|
215
215
|
files:
|
216
216
|
- lib/brick.rb
|
217
|
+
- lib/brick/compatibility.rb
|
217
218
|
- lib/brick/config.rb
|
218
219
|
- lib/brick/extensions.rb
|
219
220
|
- lib/brick/frameworks/cucumber.rb
|