brick 1.0.124 → 1.0.126
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 +9 -11
- data/lib/brick/extensions.rb +75 -30
- data/lib/brick/frameworks/rails/engine.rb +109 -264
- data/lib/brick/frameworks/rails/form_builder.rb +185 -0
- data/lib/brick/frameworks/rails/form_tags.rb +42 -28
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +19 -3
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b59690045d31108dece3222f655503a771ab860697993459a6beb92aa0ecdb07
|
4
|
+
data.tar.gz: 4c83d54ddc01e03330ac82f5949cece3f21631624adb704b923bd338ac84481c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c12e4073de8230683825617fccabeeda71746dcf3c892a60557990777543c15a3e9a18a8c7bdcf3b1c236bf422ec74d10b98bf1341b5d472e611799c8e74de31
|
7
|
+
data.tar.gz: 6e9fdb9e41ff12a1b5092226133cb7c0d8444bf330e2557da156bc7f21d4377678061bd65c5b36ad3b9d1f9bf88203594c34cf8bcaabad7b93c2609b2798e474
|
data/lib/brick/compatibility.rb
CHANGED
@@ -19,19 +19,17 @@ unless ActiveSupport.respond_to?(:version)
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
|
-
if Object.const_defined?('ActionPack')
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
::Gem::Version.new(ActionPack::VERSION::STRING)
|
27
|
-
end
|
22
|
+
if Object.const_defined?('ActionPack') && !ActionPack.respond_to?(:version)
|
23
|
+
module ActionPack
|
24
|
+
def self.version
|
25
|
+
::Gem::Version.new(ActionPack::VERSION::STRING)
|
28
26
|
end
|
29
27
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
end
|
29
|
+
if Object.const_defined?('ActionView') && !ActionView.respond_to?(:version)
|
30
|
+
module ActionView
|
31
|
+
def self.version
|
32
|
+
ActionPack.version
|
35
33
|
end
|
36
34
|
end
|
37
35
|
end
|
data/lib/brick/extensions.rb
CHANGED
@@ -68,6 +68,12 @@ module ActiveRecord
|
|
68
68
|
self
|
69
69
|
end
|
70
70
|
end
|
71
|
+
|
72
|
+
def json_column?(col)
|
73
|
+
col.type == :json || ::Brick.config.json_columns[table_name]&.include?(col.name) ||
|
74
|
+
((attr_types = attribute_types[col.name]).respond_to?(:coder) &&
|
75
|
+
(attr_types.coder.is_a?(Class) ? attr_types.coder : attr_types.coder&.class)&.name&.end_with?('JSON'))
|
76
|
+
end
|
71
77
|
end
|
72
78
|
|
73
79
|
def self._brick_primary_key(relation = nil)
|
@@ -225,6 +231,9 @@ module ActiveRecord
|
|
225
231
|
if this_obj.is_a?(ActiveRecord::Base) && (obj_descrip = this_obj.class.brick_descrip(this_obj))
|
226
232
|
this_obj = obj_descrip
|
227
233
|
end
|
234
|
+
if this_obj.is_a?(ActiveStorage::Filename) && this_obj.instance_variable_get(:@filename).nil?
|
235
|
+
this_obj.instance_variable_set(:@filename, '')
|
236
|
+
end
|
228
237
|
this_obj&.to_s || ''
|
229
238
|
end
|
230
239
|
is_brackets_have_content = true unless datum.blank?
|
@@ -671,7 +680,7 @@ module ActiveRecord
|
|
671
680
|
link_back << nm
|
672
681
|
num_bt_things += 1
|
673
682
|
# puts "BT #{a.table_name}"
|
674
|
-
"ON br_t#{idx}.
|
683
|
+
"ON br_t#{idx}.#{a.active_record.primary_key} = br_t#{idx - 1}.#{a.foreign_key}"
|
675
684
|
elsif src_ref.options[:as]
|
676
685
|
"ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
|
677
686
|
" AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
|
@@ -682,11 +691,11 @@ module ActiveRecord
|
|
682
691
|
bail_out = true
|
683
692
|
break
|
684
693
|
# "ON br_t#{idx}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
|
685
|
-
# "br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.
|
694
|
+
# "br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
|
686
695
|
else # Works for HMT through a polymorphic HO
|
687
696
|
link_back << hmt_assoc.source_reflection.inverse_of&.name # Some polymorphic "_able" thing
|
688
697
|
"ON br_t#{idx - 1}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
|
689
|
-
"br_t#{idx - 1}.#{a.foreign_key} = br_t#{idx}.
|
698
|
+
"br_t#{idx - 1}.#{a.foreign_key} = br_t#{idx}.#{a.active_record.primary_key}"
|
690
699
|
end
|
691
700
|
else # Standard has_many or has_one
|
692
701
|
# puts "HM #{a.table_name}"
|
@@ -694,7 +703,7 @@ module ActiveRecord
|
|
694
703
|
nm = hmt_assoc.source_reflection.inverse_of&.name
|
695
704
|
# )
|
696
705
|
link_back << nm # if nm
|
697
|
-
"ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.
|
706
|
+
"ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.#{a.active_record.primary_key}"
|
698
707
|
end
|
699
708
|
link_back.unshift(a.source_reflection.name)
|
700
709
|
[a.table_name, a.foreign_key, a.source_reflection.macro]
|
@@ -856,8 +865,7 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
|
|
856
865
|
alias _brick_find_sti_class find_sti_class
|
857
866
|
def find_sti_class(type_name)
|
858
867
|
if ::Brick.sti_models.key?(type_name ||= name)
|
859
|
-
|
860
|
-
_brick_find_sti_class(type_name)
|
868
|
+
::Brick.sti_models[type_name].fetch(:base, nil) || _brick_find_sti_class(type_name)
|
861
869
|
else
|
862
870
|
# This auto-STI is more of a brute-force approach, building modules where needed
|
863
871
|
# The more graceful alternative is the overload of ActiveSupport::Dependencies#autoload_module! found below
|
@@ -865,9 +873,13 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
|
|
865
873
|
module_prefixes = type_name.split('::')
|
866
874
|
module_prefixes.unshift('') unless module_prefixes.first.blank?
|
867
875
|
module_name = module_prefixes[0..-2].join('::')
|
868
|
-
if (
|
876
|
+
if (base_name = ::Brick.config.sti_namespace_prefixes&.fetch("#{module_name}::", nil)) ||
|
869
877
|
File.exist?(candidate_file = ::Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
|
870
|
-
|
878
|
+
if base_name
|
879
|
+
base_name == "::#{name}" ? self : base_name.constantize
|
880
|
+
else
|
881
|
+
_brick_find_sti_class(type_name) # Find this STI class normally
|
882
|
+
end
|
871
883
|
else
|
872
884
|
# Build missing prefix modules if they don't yet exist
|
873
885
|
this_module = Object
|
@@ -905,8 +917,23 @@ end
|
|
905
917
|
|
906
918
|
if Object.const_defined?('ActionView')
|
907
919
|
require 'brick/frameworks/rails/form_tags'
|
908
|
-
|
909
|
-
|
920
|
+
require 'brick/frameworks/rails/form_builder'
|
921
|
+
module ActionView::Helpers
|
922
|
+
module FormTagHelper
|
923
|
+
include ::Brick::Rails::FormTags
|
924
|
+
end
|
925
|
+
FormBuilder.class_exec do
|
926
|
+
include ::Brick::Rails::FormBuilder
|
927
|
+
end
|
928
|
+
end
|
929
|
+
|
930
|
+
# FormBuilder#field_id isn't available in Rails < 7.0. This is a rudimentary version with no `index`.
|
931
|
+
unless ActionView::Helpers::FormBuilder.methods.include?(:field_id)
|
932
|
+
ActionView::Helpers::FormBuilder.class_exec do
|
933
|
+
def field_id(method)
|
934
|
+
[object_name, method.to_s].join('_')
|
935
|
+
end
|
936
|
+
end
|
910
937
|
end
|
911
938
|
end
|
912
939
|
|
@@ -988,6 +1015,8 @@ Module.class_exec do
|
|
988
1015
|
end
|
989
1016
|
Object
|
990
1017
|
else
|
1018
|
+
sti_base = (::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::#{requested}", nil) ||
|
1019
|
+
::Brick.config.sti_namespace_prefixes&.fetch("::#{name}::", nil))&.constantize
|
991
1020
|
self
|
992
1021
|
end
|
993
1022
|
# puts "#{self.name} - #{args.first}"
|
@@ -995,7 +1024,7 @@ Module.class_exec do
|
|
995
1024
|
if ((is_defined = self.const_defined?(args.first)) && (possible = self.const_get(args.first)) &&
|
996
1025
|
# Reset `possible` if it's a controller request that's not a perfect match
|
997
1026
|
# Was: (possible = nil) but changed to #local_variable_set in order to suppress the "= should be ==" warning
|
998
|
-
(possible
|
1027
|
+
(possible&.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) ||
|
999
1028
|
# Try to require the respective Ruby file
|
1000
1029
|
((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
|
1001
1030
|
(self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
|
@@ -1005,9 +1034,11 @@ Module.class_exec do
|
|
1005
1034
|
# If any class has turned up so far (and we're not in the middle of eager loading)
|
1006
1035
|
# then return what we've found.
|
1007
1036
|
(is_defined && !::Brick.is_eager_loading) # Used to also have: && possible != self
|
1008
|
-
if (!brick_root && (filename || possible.instance_of?(Class))) ||
|
1009
|
-
|
1010
|
-
|
1037
|
+
if ((!brick_root && (filename || possible.instance_of?(Class))) ||
|
1038
|
+
(possible.instance_of?(Module) && possible&.module_parent == self) ||
|
1039
|
+
(possible.instance_of?(Class) && possible == self)) && # Are we simply searching for ourselves?
|
1040
|
+
# Skip when what we found as `possible` is not related to the base class of an STI model
|
1041
|
+
(!sti_base || possible.is_a?(sti_base))
|
1011
1042
|
return possible
|
1012
1043
|
end
|
1013
1044
|
end
|
@@ -1139,7 +1170,8 @@ class Object
|
|
1139
1170
|
|
1140
1171
|
def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
|
1141
1172
|
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }&.first
|
1142
|
-
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}
|
1173
|
+
if (base_model = (::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::#{class_name}", nil) || # Are we part of an auto-STI namespace? ...
|
1174
|
+
::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil))&.constantize) ||
|
1143
1175
|
base_module != Object # ... or otherwise already in some namespace?
|
1144
1176
|
schema_name = [(singular_schema_name = base_name.underscore),
|
1145
1177
|
(schema_name = singular_schema_name.pluralize),
|
@@ -1151,7 +1183,6 @@ class Object
|
|
1151
1183
|
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
|
1152
1184
|
|
1153
1185
|
if base_model
|
1154
|
-
schema_name = base_name.underscore # For the auto-STI namespace models
|
1155
1186
|
table_name = base_model.table_name
|
1156
1187
|
build_model_worker(base_module, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
|
1157
1188
|
else
|
@@ -1181,7 +1212,7 @@ class Object
|
|
1181
1212
|
full_name = if relation || schema_name.blank?
|
1182
1213
|
inheritable_name || model_name
|
1183
1214
|
else # Prefix the schema to the table name + prefix the schema namespace to the class name
|
1184
|
-
schema_module = if schema_name.
|
1215
|
+
schema_module = if schema_name.is_a?(Module) # from an auto-STI namespace?
|
1185
1216
|
schema_name
|
1186
1217
|
else
|
1187
1218
|
matching = "#{schema_name}.#{matching}"
|
@@ -1804,7 +1835,13 @@ class Object
|
|
1804
1835
|
code << " end\n"
|
1805
1836
|
self.define_method :new do
|
1806
1837
|
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
1807
|
-
|
1838
|
+
if (new_obj = model.new).respond_to?(:serializable_hash)
|
1839
|
+
# Convert any Filename objects with nil into an empty string so that #encode can be called on them
|
1840
|
+
new_obj.serializable_hash.each do |k, v|
|
1841
|
+
new_obj.send("#{k}=", ActiveStorage::Filename.new('')) if v.is_a?(ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
|
1842
|
+
end
|
1843
|
+
end
|
1844
|
+
instance_variable_set("@#{singular_table_name}".to_sym, new_obj)
|
1808
1845
|
end
|
1809
1846
|
|
1810
1847
|
params_name_sym = (params_name = "#{singular_table_name}_params").to_sym
|
@@ -1880,7 +1917,7 @@ class Object
|
|
1880
1917
|
upd_hash['invitation_accepted_at'] = nil if upd_hash['invitation_accepted_at'].blank?
|
1881
1918
|
end
|
1882
1919
|
end
|
1883
|
-
if (json_cols = model.columns.select { |c|
|
1920
|
+
if (json_cols = model.columns.select { |c| model.json_column?(c) }.map(&:name)).present?
|
1884
1921
|
upd_hash ||= upd_params.to_h
|
1885
1922
|
json_cols.each do |c|
|
1886
1923
|
begin
|
@@ -2100,7 +2137,6 @@ end.class_exec do
|
|
2100
2137
|
load apartment_initializer
|
2101
2138
|
@_apartment_loaded = true
|
2102
2139
|
end
|
2103
|
-
apartment_excluded = Apartment.excluded_models
|
2104
2140
|
end
|
2105
2141
|
# Only for Postgres (Doesn't work in sqlite3 or MySQL)
|
2106
2142
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
@@ -2190,7 +2226,7 @@ end.class_exec do
|
|
2190
2226
|
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
2191
2227
|
# is the default schema, usually 'public'.
|
2192
2228
|
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
2193
|
-
::Brick.apartment_default_tenant if
|
2229
|
+
::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(r['relation_name'])
|
2194
2230
|
elsif ![schema, 'public'].include?(r['schema'])
|
2195
2231
|
r['schema']
|
2196
2232
|
end
|
@@ -2340,7 +2376,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2340
2376
|
fk_references&.each do |fk|
|
2341
2377
|
fk = fk.values unless fk.is_a?(Array)
|
2342
2378
|
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
2343
|
-
if
|
2379
|
+
if ::Brick.is_apartment_excluded_table(::Brick.namify(fk[1]))
|
2344
2380
|
fk[0] = ::Brick.apartment_default_tenant
|
2345
2381
|
elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
|
2346
2382
|
(::Brick.is_oracle && fk[0] == schema) ||
|
@@ -2348,7 +2384,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2348
2384
|
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
|
2349
2385
|
fk[0] = nil
|
2350
2386
|
end
|
2351
|
-
if
|
2387
|
+
if ::Brick.is_apartment_excluded_table(fk[4])
|
2352
2388
|
fk[3] = ::Brick.apartment_default_tenant
|
2353
2389
|
elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
|
2354
2390
|
(::Brick.is_oracle && fk[3] == schema) ||
|
@@ -2524,8 +2560,7 @@ module Brick
|
|
2524
2560
|
# %%% Temporary schema patch
|
2525
2561
|
for_tbl = fk[1]
|
2526
2562
|
fk_namified = ::Brick.namify(fk[1])
|
2527
|
-
|
2528
|
-
fk[0] = ::Brick.apartment_default_tenant if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
|
2563
|
+
fk[0] = ::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(fk_namified)
|
2529
2564
|
fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
|
2530
2565
|
bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
|
2531
2566
|
|
@@ -2540,7 +2575,7 @@ module Brick
|
|
2540
2575
|
is_schema = if ::Brick.config.schema_behavior[:multitenant]
|
2541
2576
|
# If Apartment gem lists the primary table as being associated with a non-tenanted model
|
2542
2577
|
# then use 'public' schema for the primary table
|
2543
|
-
if
|
2578
|
+
if ::Brick.is_apartment_excluded_table(fk[4])
|
2544
2579
|
fk[3] = ::Brick.apartment_default_tenant
|
2545
2580
|
true
|
2546
2581
|
end
|
@@ -2617,7 +2652,7 @@ module Brick
|
|
2617
2652
|
end
|
2618
2653
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
2619
2654
|
else
|
2620
|
-
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] &&
|
2655
|
+
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && Object.const_defined?('Apartment') && fk[0] == ::Brick.apartment_default_tenant
|
2621
2656
|
for_tbl
|
2622
2657
|
else
|
2623
2658
|
fk[1]
|
@@ -2682,11 +2717,13 @@ module Brick
|
|
2682
2717
|
end
|
2683
2718
|
end
|
2684
2719
|
end
|
2685
|
-
::Brick.relations.
|
2686
|
-
tbl_parts =
|
2720
|
+
::Brick.relations.map do |k, v|
|
2721
|
+
tbl_parts = k.split('.')
|
2687
2722
|
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
|
2688
2723
|
res = tbl_parts.join('.')
|
2689
|
-
[
|
2724
|
+
[k, (model = models[res])&.last&.table_name || v[:class_name].constantize.table_name,
|
2725
|
+
migrations&.fetch(res, nil),
|
2726
|
+
model&.first]
|
2690
2727
|
end
|
2691
2728
|
end
|
2692
2729
|
|
@@ -2785,5 +2822,13 @@ module Brick
|
|
2785
2822
|
def _class_pk(dotted_name, multitenant)
|
2786
2823
|
Object.const_get((multitenant ? [dotted_name.split('.').last] : dotted_name.split('.')).map { |nm| "::#{nm.singularize.camelize}" }.join).primary_key
|
2787
2824
|
end
|
2825
|
+
|
2826
|
+
def is_apartment_excluded_table(tbl)
|
2827
|
+
if Object.const_defined?('Apartment')
|
2828
|
+
tbl_klass = (tnp = ::Brick.config.table_name_prefixes&.find { |k, _v| tbl.start_with?(k) }) ? +"#{tnp.last}::" : +''
|
2829
|
+
tbl_klass << tbl[tnp&.first&.length || 0..-1].singularize.camelize
|
2830
|
+
Apartment.excluded_models&.include?(tbl_klass)
|
2831
|
+
end
|
2832
|
+
end
|
2788
2833
|
end
|
2789
2834
|
end
|
@@ -2,36 +2,103 @@
|
|
2
2
|
|
3
3
|
module Brick
|
4
4
|
module Rails
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
5
|
+
class << self
|
6
|
+
def display_value(col_type, val)
|
7
|
+
is_mssql_geography = nil
|
8
|
+
# Some binary thing that really looks like a Microsoft-encoded WGS84 point? (With the first two bytes, E6 10, indicating an EPSG code of 4326)
|
9
|
+
if col_type == :binary && val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
|
10
|
+
col_type = 'geography'
|
11
|
+
is_mssql_geography = true
|
12
|
+
end
|
13
|
+
case col_type
|
14
|
+
when 'geometry', 'geography'
|
15
|
+
if Object.const_defined?('RGeo')
|
16
|
+
@is_mysql = ['Mysql2', 'Trilogy'].include?(ActiveRecord::Base.connection.adapter_name) if @is_mysql.nil?
|
17
|
+
@is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if @is_mssql.nil?
|
18
|
+
val_err = nil
|
19
|
+
|
20
|
+
if @is_mysql || (is_mssql_geography ||=
|
21
|
+
(@is_mssql ||
|
22
|
+
(val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12])
|
23
|
+
)
|
24
|
+
)
|
25
|
+
# MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
|
26
|
+
if (srid = val&.[](0..3)&.unpack('I'))
|
27
|
+
val = val.dup.force_encoding('BINARY')[4..-1].bytes
|
28
|
+
|
29
|
+
# MSSQL spatial bitwise flags, often 0C for a point:
|
30
|
+
# xxxx xxx1 = HasZValues
|
31
|
+
# xxxx xx1x = HasMValues
|
32
|
+
# xxxx x1xx = IsValid
|
33
|
+
# xxxx 1xxx = IsSinglePoint
|
34
|
+
# xxx1 xxxx = IsSingleLineSegment
|
35
|
+
# xx1x xxxx = IsWholeGlobe
|
36
|
+
# Convert Microsoft's unique geography binary to standard WKB
|
37
|
+
# (MSSQL point usually has two doubles, lng / lat, and can also have Z)
|
38
|
+
if is_mssql_geography
|
39
|
+
if val[0] == 1 && (val[1] & 8 > 0) && # Single point?
|
40
|
+
(val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
|
41
|
+
val = [0, 0, 0, 0, 1] + val[2..-1].reverse
|
42
|
+
else
|
43
|
+
val_err = '(Microsoft internal SQL geography type)'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
unless val_err || val.nil?
|
49
|
+
if (geometry = RGeo::WKRep::WKBParser.new.parse(val.pack('c*'))).is_a?(RGeo::Cartesian::PointImpl) &&
|
50
|
+
!(geometry.y == 0.0 && geometry.x == 0.0)
|
51
|
+
# Create a POINT link to this style of Google maps URL: https://www.google.com/maps/place/38.7071296+-121.2810649/@38.7071296,-121.2810649,12z
|
52
|
+
geometry = "<a href=\"https://www.google.com/maps/place/#{geometry.y}+#{geometry.x}/@#{geometry.y},#{geometry.x},12z\" target=\"blank\">#{geometry.to_s}</a>"
|
53
|
+
end
|
54
|
+
val = geometry
|
55
|
+
end
|
56
|
+
val_err || val
|
57
|
+
else
|
58
|
+
'(Add RGeo gem to parse geometry detail)'
|
59
|
+
end
|
60
|
+
when :binary
|
61
|
+
::Brick::Rails.display_binary(val) if val
|
62
|
+
else
|
63
|
+
if col_type
|
64
|
+
::Brick::Rails::FormBuilder.hide_bcrypt(val, col_type == :xml)
|
65
|
+
else
|
66
|
+
'?'
|
67
|
+
end
|
68
|
+
end
|
25
69
|
end
|
26
70
|
|
27
|
-
|
28
|
-
|
29
|
-
|
71
|
+
def display_binary(val)
|
72
|
+
@image_signatures ||= { (+"\xFF\xD8\xFF\xEE").force_encoding('ASCII-8BIT') => 'jpeg',
|
73
|
+
(+"\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01").force_encoding('ASCII-8BIT') => 'jpeg',
|
74
|
+
(+"\x89PNG\r\n\x1A\n").force_encoding('ASCII-8BIT') => 'png',
|
75
|
+
'<svg' => 'svg+xml', # %%% Not yet very good detection for SVG
|
76
|
+
(+'BM').force_encoding('ASCII-8BIT') => 'bmp',
|
77
|
+
(+'GIF87a').force_encoding('ASCII-8BIT') => 'gif',
|
78
|
+
(+'GIF89a').force_encoding('ASCII-8BIT') => 'gif' }
|
79
|
+
|
80
|
+
if val[0..1] == "\x15\x1C" # One of those goofy Microsoft OLE containers?
|
81
|
+
package_header_length = val[2..3].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
82
|
+
# This will often be just FF FF FF FF
|
83
|
+
# object_size = val[16..19].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
84
|
+
friendly_and_class_names = val[20...package_header_length].split("\0")
|
85
|
+
object_type_name_length = val[package_header_length + 8..package_header_length+11].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
86
|
+
friendly_and_class_names << val[package_header_length + 12...package_header_length + 12 + object_type_name_length].strip
|
87
|
+
# friendly_and_class_names will now be something like: ['Bitmap Image', 'Paint.Picture', 'PBrush']
|
88
|
+
real_object_size = val[package_header_length + 20 + object_type_name_length..package_header_length + 23 + object_type_name_length].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
89
|
+
object_start = package_header_length + 24 + object_type_name_length
|
90
|
+
val = val[object_start...object_start + real_object_size]
|
91
|
+
end
|
92
|
+
|
93
|
+
if (signature = @image_signatures.find { |k, _v| val[0...k.length] == k })
|
94
|
+
if val.length < 500_000
|
95
|
+
"<img src=\"data:image/#{signature.last};base64,#{Base64.encode64(val)}\">"
|
96
|
+
else
|
97
|
+
"< #{signature.last} image, #{val.length} bytes >"
|
98
|
+
end
|
30
99
|
else
|
31
|
-
"< 
|
100
|
+
"< Binary, #{val.length} bytes >"
|
32
101
|
end
|
33
|
-
else
|
34
|
-
"< Binary, #{val.length} bytes >"
|
35
102
|
end
|
36
103
|
end
|
37
104
|
|
@@ -878,138 +945,7 @@ input+svg.revert {
|
|
878
945
|
window.addEventListener(\"popstate\", function () { location.reload(true); });
|
879
946
|
</script>
|
880
947
|
|
881
|
-
<%
|
882
|
-
is_includes_json = nil
|
883
|
-
is_includes_text = nil
|
884
|
-
def is_bcrypt?(val)
|
885
|
-
val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
|
886
|
-
end
|
887
|
-
def hide_bcrypt(val, is_xml = nil, max_len = 200)
|
888
|
-
if is_bcrypt?(val)
|
889
|
-
'(hidden)'
|
890
|
-
else
|
891
|
-
if val.is_a?(String)
|
892
|
-
val = val.dup.force_encoding('UTF-8').strip
|
893
|
-
return CGI.escapeHTML(val) if is_xml
|
894
|
-
|
895
|
-
if val.length > max_len
|
896
|
-
if val[0] == '<' # Seems to be HTML?
|
897
|
-
cur_len = 0
|
898
|
-
cur_idx = 0
|
899
|
-
# Find which HTML tags we might be inside so we can apply ending tags to balance
|
900
|
-
element_name = nil
|
901
|
-
in_closing = nil
|
902
|
-
elements = []
|
903
|
-
val.each_char do |ch|
|
904
|
-
case ch
|
905
|
-
when '<'
|
906
|
-
element_name = +''
|
907
|
-
when '/' # First character of tag is '/'?
|
908
|
-
in_closing = true if element_name == ''
|
909
|
-
when '>'
|
910
|
-
if element_name
|
911
|
-
if in_closing
|
912
|
-
if (idx = elements.index { |tag| tag.downcase == element_name.downcase })
|
913
|
-
elements.delete_at(idx)
|
914
|
-
end
|
915
|
-
elsif (tag_name = element_name.split.first).present?
|
916
|
-
elements.unshift(tag_name)
|
917
|
-
end
|
918
|
-
element_name = nil
|
919
|
-
in_closing = nil
|
920
|
-
end
|
921
|
-
else
|
922
|
-
element_name << ch if element_name
|
923
|
-
end
|
924
|
-
cur_idx += 1
|
925
|
-
# Unless it's inside wickets then this is real text content, and see if we're at the limit
|
926
|
-
break if element_name.nil? && ((cur_len += 1) > max_len)
|
927
|
-
end
|
928
|
-
val = val[0..cur_idx]
|
929
|
-
# Somehow still in the middle of an opening tag right at the end? (Should never happen)
|
930
|
-
if !in_closing && (tag_name = element_name&.split&.first)&.present?
|
931
|
-
elements.unshift(tag_name)
|
932
|
-
val << '>'
|
933
|
-
end
|
934
|
-
elements.each do |closing_tag|
|
935
|
-
val << \"</#\{closing_tag}>\"
|
936
|
-
end
|
937
|
-
else # Not HTML, just cut it at the length
|
938
|
-
val = val[0...max_len]
|
939
|
-
end
|
940
|
-
val = \"#\{val}...\"
|
941
|
-
end
|
942
|
-
val
|
943
|
-
else
|
944
|
-
val.to_s
|
945
|
-
end
|
946
|
-
end
|
947
|
-
end
|
948
|
-
def display_value(col_type, val)
|
949
|
-
is_mssql_geography = nil
|
950
|
-
# Some binary thing that really looks like a Microsoft-encoded WGS84 point? (With the first two bytes, E6 10, indicating an EPSG code of 4326)
|
951
|
-
if col_type == :binary && val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
|
952
|
-
col_type = 'geography'
|
953
|
-
is_mssql_geography = true
|
954
|
-
end
|
955
|
-
case col_type
|
956
|
-
when 'geometry', 'geography'
|
957
|
-
if Object.const_defined?('RGeo')
|
958
|
-
@is_mysql = ['Mysql2', 'Trilogy'].include?(ActiveRecord::Base.connection.adapter_name) if @is_mysql.nil?
|
959
|
-
@is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if @is_mssql.nil?
|
960
|
-
val_err = nil
|
961
|
-
|
962
|
-
if @is_mysql || (is_mssql_geography ||=
|
963
|
-
(@is_mssql ||
|
964
|
-
(val && val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12])
|
965
|
-
)
|
966
|
-
)
|
967
|
-
# MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
|
968
|
-
if (srid = val&.[](0..3)&.unpack('I'))
|
969
|
-
val = val.dup.force_encoding('BINARY')[4..-1].bytes
|
970
|
-
|
971
|
-
# MSSQL spatial bitwise flags, often 0C for a point:
|
972
|
-
# xxxx xxx1 = HasZValues
|
973
|
-
# xxxx xx1x = HasMValues
|
974
|
-
# xxxx x1xx = IsValid
|
975
|
-
# xxxx 1xxx = IsSinglePoint
|
976
|
-
# xxx1 xxxx = IsSingleLineSegment
|
977
|
-
# xx1x xxxx = IsWholeGlobe
|
978
|
-
# Convert Microsoft's unique geography binary to standard WKB
|
979
|
-
# (MSSQL point usually has two doubles, lng / lat, and can also have Z)
|
980
|
-
if is_mssql_geography
|
981
|
-
if val[0] == 1 && (val[1] & 8 > 0) && # Single point?
|
982
|
-
(val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
|
983
|
-
val = [0, 0, 0, 0, 1] + val[2..-1].reverse
|
984
|
-
else
|
985
|
-
val_err = '(Microsoft internal SQL geography type)'
|
986
|
-
end
|
987
|
-
end
|
988
|
-
end
|
989
|
-
end
|
990
|
-
unless val_err || val.nil?
|
991
|
-
if (geometry = RGeo::WKRep::WKBParser.new.parse(val.pack('c*'))).is_a?(RGeo::Cartesian::PointImpl) &&
|
992
|
-
!(geometry.y == 0.0 && geometry.x == 0.0)
|
993
|
-
# Create a POINT link to this style of Google maps URL: https://www.google.com/maps/place/38.7071296+-121.2810649/@38.7071296,-121.2810649,12z
|
994
|
-
geometry = \"<a href=\\\"https://www.google.com/maps/place/#\{geometry.y}+#\{geometry.x}/@#\{geometry.y},#\{geometry.x},12z\\\" target=\\\"blank\\\">#\{geometry.to_s}</a>\"
|
995
|
-
end
|
996
|
-
val = geometry
|
997
|
-
end
|
998
|
-
val_err || val
|
999
|
-
else
|
1000
|
-
'(Add RGeo gem to parse geometry detail)'
|
1001
|
-
end
|
1002
|
-
when :binary
|
1003
|
-
::Brick::Rails.display_binary(val) if val
|
1004
|
-
else
|
1005
|
-
if col_type
|
1006
|
-
hide_bcrypt(val, col_type == :xml)
|
1007
|
-
else
|
1008
|
-
'?'
|
1009
|
-
end
|
1010
|
-
end
|
1011
|
-
end
|
1012
|
-
|
948
|
+
<%
|
1013
949
|
# Accommodate composite primary keys that include strings with forward-slash characters
|
1014
950
|
def slashify(*vals)
|
1015
951
|
vals.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
|
@@ -1245,6 +1181,7 @@ erDiagram
|
|
1245
1181
|
}
|
1246
1182
|
<% end
|
1247
1183
|
# callback < %= cb_k % > erdClick
|
1184
|
+
@_brick_monetized_attributes = model.respond_to?(:monetized_attributes) ? model.monetized_attributes.values : {}
|
1248
1185
|
%>
|
1249
1186
|
</div>
|
1250
1187
|
"
|
@@ -1568,10 +1505,10 @@ end
|
|
1568
1505
|
<title><%=
|
1569
1506
|
base_model = (model = (obj = @#{obj_name})&.class).base_class
|
1570
1507
|
see_all_path = send(\"#\{base_model._brick_index}_path\")
|
1571
|
-
|
1572
|
-
|
1508
|
+
#{(inh_col = @_brick_model.inheritance_column).present? &&
|
1509
|
+
" if obj.respond_to?(:#{inh_col}) && (model_name = @#{obj_name}.#{inh_col}) != base_model.name
|
1573
1510
|
see_all_path << \"?#{inh_col}=#\{model_name}\"
|
1574
|
-
end
|
1511
|
+
end"}
|
1575
1512
|
page_title = (\"#\{model_name ||= model.name}: #\{obj&.brick_descrip || controller_name}\")
|
1576
1513
|
%></title>
|
1577
1514
|
</head>
|
@@ -1612,7 +1549,7 @@ end
|
|
1612
1549
|
end %>
|
1613
1550
|
</table>
|
1614
1551
|
<%
|
1615
|
-
if (description = (relation = Brick.relations[
|
1552
|
+
if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
|
1616
1553
|
description %><br><%
|
1617
1554
|
end
|
1618
1555
|
%><%= link_to \"(See all #\{model_name.pluralize})\", see_all_path %>
|
@@ -1673,100 +1610,7 @@ end
|
|
1673
1610
|
<% end %>
|
1674
1611
|
</th>
|
1675
1612
|
<td>
|
1676
|
-
|
1677
|
-
<% dt_pickers = { datetime: 'datetimepicker', timestamp: 'datetimepicker', time: 'timepicker', date: 'datepicker' }
|
1678
|
-
html_options = {}
|
1679
|
-
html_options[:class] = 'dimmed' unless val
|
1680
|
-
is_revert = true
|
1681
|
-
if bt
|
1682
|
-
html_options[:prompt] = \"Select #\{bt_name\}\" %>
|
1683
|
-
<%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
|
1684
|
-
<%= if (bt_obj = bt_class&.find_by(bt_pair[1] => val))
|
1685
|
-
link_to('⇛', send(\"#\{bt_class.base_class._brick_index(:singular)\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' })
|
1686
|
-
elsif val
|
1687
|
-
\"<span class=\\\"orphan\\\">Orphaned ID: #\{val}</span>\".html_safe
|
1688
|
-
end %>
|
1689
|
-
<% else
|
1690
|
-
col_type = if ::Brick.config.json_columns[tbl_name]&.include?(k)
|
1691
|
-
:json
|
1692
|
-
elsif col&.sql_type == 'geography'
|
1693
|
-
col.sql_type
|
1694
|
-
else
|
1695
|
-
col&.type
|
1696
|
-
end
|
1697
|
-
case (col_type ||= col&.sql_type)
|
1698
|
-
when :string, :text
|
1699
|
-
if is_bcrypt?(val) # || .readonly?
|
1700
|
-
is_revert = false %>
|
1701
|
-
<%= hide_bcrypt(val, nil, 1000) %>
|
1702
|
-
<% elsif col_type == :string
|
1703
|
-
if model.respond_to?(:enumerized_attributes) && (opts = model.enumerized_attributes[k]&.options).present? %>
|
1704
|
-
<%= f.select(k.to_sym, [[\"(No #\{k} chosen)\", '^^^brick_NULL^^^']] + opts, { value: val || '^^^brick_NULL^^^' }, html_options) %><%
|
1705
|
-
else %>
|
1706
|
-
<%= f.text_field(k.to_sym, html_options) %><%
|
1707
|
-
end
|
1708
|
-
else
|
1709
|
-
is_includes_text = true %>
|
1710
|
-
<%= f.hidden_field(k.to_sym, html_options) %>
|
1711
|
-
<trix-editor input=\"<%= f.field_id(k) %>\"></trix-editor>
|
1712
|
-
<% end %>
|
1713
|
-
<% when :boolean %>
|
1714
|
-
<%= f.check_box k.to_sym %>
|
1715
|
-
<% when :integer, :decimal, :float %>
|
1716
|
-
<%= digit_pattern = col_type == :integer ? '\\d*' : '\\d*(?:\\.\\d*|)'
|
1717
|
-
# Used to do this for float / decimal: f.number_field k.to_sym
|
1718
|
-
f.text_field k.to_sym, { pattern: digit_pattern, class: 'check-validity' } %>
|
1719
|
-
<% when *dt_pickers.keys
|
1720
|
-
is_includes_dates = true %>
|
1721
|
-
<%= f.text_field k.to_sym, { class: dt_pickers[col_type] } %>
|
1722
|
-
<% when :uuid
|
1723
|
-
is_revert = false %>
|
1724
|
-
<%=
|
1725
|
-
# Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
|
1726
|
-
# If it's not yet enabled then: create extension \"uuid-ossp\";
|
1727
|
-
# ActiveUUID gem created a new :uuid type
|
1728
|
-
val %>
|
1729
|
-
<% when :ltree %>
|
1730
|
-
<%=
|
1731
|
-
# In Postgres labels of data stored in a hierarchical tree-like structure
|
1732
|
-
# If it's not yet enabled then: create extension ltree;
|
1733
|
-
val %>
|
1734
|
-
<% when :binary %>
|
1735
|
-
<%= is_revert = false
|
1736
|
-
if val
|
1737
|
-
# %%% This same kind of geography check is done two other times above ... would be great to DRY it up.
|
1738
|
-
if val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
|
1739
|
-
display_value('geography', val)
|
1740
|
-
else
|
1741
|
-
::Brick::Rails.display_binary(val)
|
1742
|
-
end.html_safe
|
1743
|
-
end %>
|
1744
|
-
<% when :primary_key
|
1745
|
-
is_revert = false %>
|
1746
|
-
<% when :json
|
1747
|
-
is_includes_json = true
|
1748
|
-
if val.is_a?(String)
|
1749
|
-
val_str = val
|
1750
|
-
else
|
1751
|
-
eheij = ActiveSupport::JSON::Encoding.escape_html_entities_in_json
|
1752
|
-
ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false if eheij
|
1753
|
-
val_str = val.to_json
|
1754
|
-
ActiveSupport::JSON::Encoding.escape_html_entities_in_json = eheij
|
1755
|
-
end %>
|
1756
|
-
<%= # Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
|
1757
|
-
# (and previous to this, escape backticks with our own goofy code of ^^br_btick__ )
|
1758
|
-
json_field = f.hidden_field k.to_sym, { class: 'jsonpicker', value: val_str.gsub('`', '^^br_btick__').tr('\"', '`').html_safe } %>
|
1759
|
-
<div id=\"_br_json_<%= f.field_id(k) %>\"></div>
|
1760
|
-
<% else %>
|
1761
|
-
<%= is_revert = false
|
1762
|
-
display_value(col_type, val).html_safe %>
|
1763
|
-
<% end
|
1764
|
-
end
|
1765
|
-
if is_revert
|
1766
|
-
%></td>
|
1767
|
-
<td><svg class=\"revert\" width=\"1.5em\" viewBox=\"0 0 512 512\"><use xlink:href=\"#revertPath\" /></svg>
|
1768
|
-
<% end %>
|
1769
|
-
</td></tr></table>
|
1613
|
+
<%= f.brick_field(k, html_options = {}, val, col, bt, bt_class, bt_name, bt_pair) %>
|
1770
1614
|
</td>
|
1771
1615
|
</tr>
|
1772
1616
|
<% end
|
@@ -1795,11 +1639,12 @@ end
|
|
1795
1639
|
# with a .where(). And the polymorphic class name it points to is the base class name of
|
1796
1640
|
# the STI model instead of its subclass.
|
1797
1641
|
poly_type = #{poly_type.inspect}
|
1798
|
-
|
1642
|
+
#{ (inh_col = @_brick_model.inheritance_column).present? &&
|
1643
|
+
" if poly_type && @#{obj_name}.respond_to?(:#{inh_col}) &&
|
1799
1644
|
(base_type = collection.where_values_hash[poly_type])
|
1800
|
-
collection = collection.rewhere(poly_type => [base_type, @#{obj_name}.#{
|
1801
|
-
end"
|
1802
|
-
|
1645
|
+
collection = collection.rewhere(poly_type => [base_type, @#{obj_name}.#{inh_col}])
|
1646
|
+
end"}"
|
1647
|
+
end
|
1803
1648
|
s << "<table id=\"#{hm_name}\" class=\"shadow\">
|
1804
1649
|
<tr><th>#{hm[1]}#{' poly' if hm[0].options[:as]} #{hm[3]}</th></tr>
|
1805
1650
|
<% collection = @#{obj_name}.#{hm_name}
|
@@ -1840,7 +1685,7 @@ end}
|
|
1840
1685
|
end
|
1841
1686
|
unless is_crosstab
|
1842
1687
|
inline << "
|
1843
|
-
<% if
|
1688
|
+
<% if @_date_fields_present %>
|
1844
1689
|
<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css\">
|
1845
1690
|
<style>
|
1846
1691
|
.flatpickr-calendar {
|
@@ -1855,18 +1700,18 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
|
|
1855
1700
|
</script>
|
1856
1701
|
<% end %>
|
1857
1702
|
|
1858
|
-
<% if false #
|
1703
|
+
<% if false # @_dropdown_fields_present %>
|
1859
1704
|
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/slim-select/1.27.1/slimselect.min.js\"></script>
|
1860
1705
|
<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdnjs.cloudflare.com/ajax/libs/slim-select/1.27.1/slimselect.min.css\">
|
1861
1706
|
<% end %>
|
1862
1707
|
|
1863
|
-
<% if
|
1708
|
+
<% if @_text_fields_present %>
|
1864
1709
|
<script src=\"https://cdn.jsdelivr.net/npm/trix@2.0/dist/trix.umd.min.js\"></script>
|
1865
1710
|
<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/trix@2.0/dist/trix.min.css\">
|
1866
1711
|
<% end %>
|
1867
1712
|
|
1868
1713
|
<% # Started with v0.14.4 of vanilla-jsoneditor
|
1869
|
-
if
|
1714
|
+
if @_json_fields_present %>
|
1870
1715
|
<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor/themes/jse-theme-default.min.css\">
|
1871
1716
|
<script type=\"module\">
|
1872
1717
|
import { JSONEditor } from \"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor/index.min.js\";
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module Brick::Rails::FormBuilder
|
2
|
+
DT_PICKERS = { datetime: 'datetimepicker', timestamp: 'datetimepicker', time: 'timepicker', date: 'datepicker' }
|
3
|
+
|
4
|
+
# When this field is one of the appropriate types, will set one of these instance variables accordingly:
|
5
|
+
# @_text_fields_present - To include trix editor
|
6
|
+
# @_date_fields_present - To include flatpickr date / time editor
|
7
|
+
# @_json_fields_present - To include JSONEditor
|
8
|
+
def brick_field(method, html_options = {}, val = nil, col = nil, bt = nil, bt_class = nil, bt_name = nil, bt_pair = nil)
|
9
|
+
model = self.object.class
|
10
|
+
col ||= model.columns_hash[method]
|
11
|
+
out = +'<table><tr><td>'
|
12
|
+
html_options[:class] = 'dimmed' unless val
|
13
|
+
is_revert = true
|
14
|
+
template = instance_variable_get(:@template)
|
15
|
+
if bt
|
16
|
+
bt_class ||= bt[1].first.first
|
17
|
+
bt_name ||= bt[1].map { |x| x.first.name }.join('/')
|
18
|
+
bt_pair ||= bt[1].first
|
19
|
+
|
20
|
+
html_options[:prompt] = "Select #{bt_name}"
|
21
|
+
out << self.select(method.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options)
|
22
|
+
out << if (bt_obj = bt_class&.find_by(bt_pair[1] => val))
|
23
|
+
bt_path = template.send(
|
24
|
+
"#{bt_class.base_class._brick_index(:singular)}_path".to_sym,
|
25
|
+
bt_obj.send(bt_class.primary_key.to_sym)
|
26
|
+
)
|
27
|
+
template.link_to('⇛', bt_path, { class: 'show-arrow' })
|
28
|
+
elsif val
|
29
|
+
"<span class=\"orphan\">Orphaned ID: #{val}</span>".html_safe
|
30
|
+
end
|
31
|
+
elsif @_brick_monetized_attributes&.include?(method)
|
32
|
+
out << self.text_field(method.to_sym, html_options.merge({ value: Money.new(val.to_i).format }))
|
33
|
+
else
|
34
|
+
col_type = if model.json_column?(col) || val.is_a?(Array)
|
35
|
+
:json
|
36
|
+
elsif col&.sql_type == 'geography'
|
37
|
+
col.sql_type
|
38
|
+
else
|
39
|
+
col&.type
|
40
|
+
end
|
41
|
+
case (col_type ||= col&.sql_type)
|
42
|
+
when :string, :text
|
43
|
+
if ::Brick::Rails::FormBuilder.is_bcrypt?(val) # || .readonly?
|
44
|
+
is_revert = false
|
45
|
+
out << ::Brick::Rails::FormBuilder.hide_bcrypt(val, nil, 1000)
|
46
|
+
elsif col_type == :string
|
47
|
+
if model.respond_to?(:enumerized_attributes) && (opts = (attr = model.enumerized_attributes[method])&.options).present?
|
48
|
+
enum_html_options = attr.kind_of?(Enumerize::Multiple) ? html_options.merge({ multiple: true, size: opts.length + 1 }) : html_options
|
49
|
+
out << self.select(method.to_sym, [["(No #{method} chosen)", '^^^brick_NULL^^^']] + opts, { value: val || '^^^brick_NULL^^^' }, enum_html_options)
|
50
|
+
else
|
51
|
+
out << self.text_field(method.to_sym, html_options)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
template.instance_variable_set(:@_text_fields_present, true)
|
55
|
+
out << self.hidden_field(method.to_sym, html_options)
|
56
|
+
out << "<trix-editor input=\"#{self.field_id(method)}\"></trix-editor>"
|
57
|
+
end
|
58
|
+
when :boolean
|
59
|
+
out << self.check_box(method.to_sym)
|
60
|
+
when :integer, :decimal, :float
|
61
|
+
digit_pattern = col_type == :integer ? '\d*' : '\d*(?:\.\d*|)'
|
62
|
+
# Used to do this for float / decimal: self.number_field method.to_sym
|
63
|
+
out << self.text_field(method.to_sym, { pattern: digit_pattern, class: 'check-validity' })
|
64
|
+
when *DT_PICKERS.keys
|
65
|
+
template.instance_variable_set(:@_date_fields_present, true)
|
66
|
+
out << self.text_field(method.to_sym, { class: DT_PICKERS[col_type] })
|
67
|
+
when :uuid
|
68
|
+
is_revert = false
|
69
|
+
# Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
|
70
|
+
# If it's not yet enabled then: create extension \"uuid-ossp\";
|
71
|
+
# ActiveUUID gem created a new :uuid type
|
72
|
+
out << val
|
73
|
+
when :ltree
|
74
|
+
# In Postgres labels of data stored in a hierarchical tree-like structure
|
75
|
+
# If it's not yet enabled then: create extension ltree;
|
76
|
+
out << val
|
77
|
+
when :binary
|
78
|
+
is_revert = false
|
79
|
+
if val
|
80
|
+
# %%% This same kind of geography check is done two other times in engine.rb ... would be great to DRY it up.
|
81
|
+
out << if val.length < 31 && (val.length - 6) % 8 == 0 && val[0..5].bytes == [230, 16, 0, 0, 1, 12]
|
82
|
+
::Brick::Rails.display_value('geography', val)
|
83
|
+
else
|
84
|
+
::Brick::Rails.display_binary(val)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
when :primary_key
|
88
|
+
is_revert = false
|
89
|
+
when :json
|
90
|
+
template.instance_variable_set(:@_json_fields_present, true)
|
91
|
+
if val.is_a?(String)
|
92
|
+
val_str = val
|
93
|
+
else
|
94
|
+
eheij = ActiveSupport::JSON::Encoding.escape_html_entities_in_json
|
95
|
+
ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false if eheij
|
96
|
+
val_str = val.to_json
|
97
|
+
ActiveSupport::JSON::Encoding.escape_html_entities_in_json = eheij
|
98
|
+
end
|
99
|
+
# Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
|
100
|
+
# (and previous to this, escape backticks with our own goofy code of ^^br_btick__ )
|
101
|
+
out << (json_field = self.hidden_field(method.to_sym, { class: 'jsonpicker', value: val_str.gsub('`', '^^br_btick__').tr('\"', '`').html_safe }))
|
102
|
+
out << "<div id=\"_br_json_#{self.field_id(method)}\"></div>"
|
103
|
+
else
|
104
|
+
is_revert = false
|
105
|
+
out << ::Brick::Rails.display_value(col_type, val).html_safe
|
106
|
+
end
|
107
|
+
end
|
108
|
+
if is_revert
|
109
|
+
out << "</td>
|
110
|
+
"
|
111
|
+
out << '<td><svg class="revert" width="1.5em" viewBox="0 0 512 512"><use xlink:href="#revertPath" /></svg>'
|
112
|
+
end
|
113
|
+
out << "</td></tr></table>
|
114
|
+
"
|
115
|
+
out.html_safe
|
116
|
+
end # brick_field
|
117
|
+
|
118
|
+
# --- CLASS METHODS ---
|
119
|
+
|
120
|
+
def self.is_bcrypt?(val)
|
121
|
+
val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.hide_bcrypt(val, is_xml = nil, max_len = 200)
|
125
|
+
if ::Brick::Rails::FormBuilder.is_bcrypt?(val)
|
126
|
+
'(hidden)'
|
127
|
+
else
|
128
|
+
if val.is_a?(String)
|
129
|
+
val = val.dup.force_encoding('UTF-8').strip
|
130
|
+
return CGI.escapeHTML(val) if is_xml
|
131
|
+
|
132
|
+
if val.length > max_len
|
133
|
+
if val[0] == '<' # Seems to be HTML?
|
134
|
+
cur_len = 0
|
135
|
+
cur_idx = 0
|
136
|
+
# Find which HTML tags we might be inside so we can apply ending tags to balance
|
137
|
+
element_name = nil
|
138
|
+
in_closing = nil
|
139
|
+
elements = []
|
140
|
+
val.each_char do |ch|
|
141
|
+
case ch
|
142
|
+
when '<'
|
143
|
+
element_name = +''
|
144
|
+
when '/' # First character of tag is '/'?
|
145
|
+
in_closing = true if element_name == ''
|
146
|
+
when '>'
|
147
|
+
if element_name
|
148
|
+
if in_closing
|
149
|
+
if (idx = elements.index { |tag| tag.downcase == element_name.downcase })
|
150
|
+
elements.delete_at(idx)
|
151
|
+
end
|
152
|
+
elsif (tag_name = element_name.split.first).present?
|
153
|
+
elements.unshift(tag_name)
|
154
|
+
end
|
155
|
+
element_name = nil
|
156
|
+
in_closing = nil
|
157
|
+
end
|
158
|
+
else
|
159
|
+
element_name << ch if element_name
|
160
|
+
end
|
161
|
+
cur_idx += 1
|
162
|
+
# Unless it's inside wickets then this is real text content, and see if we're at the limit
|
163
|
+
break if element_name.nil? && ((cur_len += 1) > max_len)
|
164
|
+
end
|
165
|
+
val = val[0..cur_idx]
|
166
|
+
# Somehow still in the middle of an opening tag right at the end? (Should never happen)
|
167
|
+
if !in_closing && (tag_name = element_name&.split&.first)&.present?
|
168
|
+
elements.unshift(tag_name)
|
169
|
+
val << '>'
|
170
|
+
end
|
171
|
+
elements.each do |closing_tag|
|
172
|
+
val << "</#{closing_tag}>"
|
173
|
+
end
|
174
|
+
else # Not HTML, just cut it at the length
|
175
|
+
val = val[0...max_len]
|
176
|
+
end
|
177
|
+
val = "#{val}..."
|
178
|
+
end
|
179
|
+
val
|
180
|
+
else
|
181
|
+
val.to_s
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -20,40 +20,50 @@ module Brick::Rails::FormTags
|
|
20
20
|
s << col_name
|
21
21
|
cols[col_name] = col
|
22
22
|
end
|
23
|
+
composite_bts = bts.select { |k, _v| k.is_a?(Array) }
|
24
|
+
composite_bt_names = {}
|
25
|
+
composite_bt_cols = composite_bts.each_with_object([]) do |bt, s|
|
26
|
+
composite_bt_names[bt.first.join('__')] = bt.last
|
27
|
+
bt.first.each { |bt_col| s << bt_col unless s.include?(bt_col.first) }
|
28
|
+
end
|
23
29
|
unless sequence # If no sequence is defined, start with all inclusions
|
24
30
|
cust_cols = klass._br_cust_cols
|
25
31
|
# HOT columns, kept as symbols
|
26
32
|
hots = klass._br_bt_descrip.keys.select { |k| bts.key?(k) }
|
27
|
-
sequence = col_keys
|
33
|
+
sequence = (col_keys - composite_bt_cols) +
|
34
|
+
composite_bt_names.keys + cust_cols.keys + hots +
|
35
|
+
hms_keys.reject { |assoc_name| inclusions&.exclude?(assoc_name) }
|
28
36
|
end
|
29
37
|
sequence.reject! { |nm| exclusions.include?(nm) } if exclusions
|
30
38
|
out << sequence.each_with_object(+'') do |col_name, s|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
if (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
|
40
|
+
s << '<th '
|
41
|
+
s << "title=\"#{col.comment}\"" if col.respond_to?(:comment) && !col.comment.blank?
|
42
|
+
s << if (bt = bts[col_name])
|
43
|
+
# Allow sorting for any BT except polymorphics
|
44
|
+
"x-order=\"#{bt.first.to_s + '"' unless bt[2]}>BT " +
|
45
|
+
bt[1].map { |bt_pair| bt_pair.first.bt_link(bt.first) }.join(' ')
|
46
|
+
else # Normal column
|
47
|
+
"x-order=\"#{col_name + '"' if true}>#{col_name}"
|
48
|
+
end
|
49
|
+
elsif col # HM column
|
50
|
+
options = {}
|
51
|
+
options[col[1].inheritance_column] = col[1].name unless col[1] == col[1].base_class
|
52
|
+
s << "<th x-order=\"#{col_name + '"' if true}>#{col[2]} "
|
53
|
+
s << (col.first ? "#{col[3]}" : "#{link_to(col[3], send("#{col[1]._brick_index}_path", options))}")
|
54
|
+
elsif cust_cols.key?(col_name) # Custom column
|
55
|
+
s << "<th x-order=\"#{col_name}\">#{col_name}"
|
56
|
+
elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
|
57
|
+
s << "<th x-order=\"#{hot.first.to_s}\">HOT " +
|
58
|
+
hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
|
59
|
+
elsif (bt = composite_bt_names[col_name])
|
60
|
+
s << "<th x-order=\"#{bt.first.to_s + '"' unless bt[2]}>BT comp " +
|
37
61
|
bt[1].map { |bt_pair| bt_pair.first.bt_link(bt.first) }.join(' ')
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
options = {}
|
43
|
-
options[col[1].inheritance_column] = col[1].name unless col[1] == col[1].base_class
|
44
|
-
s << "<th#{' x-order="' + col_name + '"' if true}>#{col[2]} "
|
45
|
-
s << (col.first ? "#{col[3]}" : "#{link_to(col[3], send("#{col[1]._brick_index}_path", options))}")
|
46
|
-
elsif cust_cols.key?(col_name) # Custom column
|
47
|
-
s << "<th x-order=\"#{col_name}\">#{col_name}"
|
48
|
-
elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
|
49
|
-
s << "<th x-order=\"#{hot.first.to_s}\">HOT " +
|
50
|
-
hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
|
51
|
-
hot[1].first
|
52
|
-
else # Bad column name!
|
53
|
-
s << "<th title=\"<< Unknown column >>\">#{col_name}"
|
62
|
+
else # Bad column name!
|
63
|
+
s << "<th title=\"<< Unknown column >>\">#{col_name}"
|
64
|
+
end
|
65
|
+
s << '</th>'
|
54
66
|
end
|
55
|
-
s << '</th>'
|
56
|
-
end
|
57
67
|
out << "</tr></thead>
|
58
68
|
<tbody>"
|
59
69
|
# %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
|
@@ -70,7 +80,7 @@ module Brick::Rails::FormTags
|
|
70
80
|
out << ' class=\"dimmed\"' unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
|
71
81
|
(col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
|
72
82
|
out << '>'
|
73
|
-
if (bt = bts[col_name])
|
83
|
+
if (bt = bts[col_name] || composite_bt_names[col_name])
|
74
84
|
if bt[2] # Polymorphic?
|
75
85
|
if (poly_id = obj.send("#{bt.first}_id"))
|
76
86
|
# Was: obj.send("#{bt.first}_type")
|
@@ -119,8 +129,12 @@ module Brick::Rails::FormTags
|
|
119
129
|
end
|
120
130
|
elsif (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
|
121
131
|
# binding.pry if col.is_a?(Array)
|
122
|
-
|
123
|
-
|
132
|
+
out << if @_brick_monetized_attributes&.include?(col_name)
|
133
|
+
val ? Money.new(val.to_i).format : ''
|
134
|
+
else
|
135
|
+
col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
|
136
|
+
::Brick::Rails.display_value(col_type || col&.sql_type, val).to_s
|
137
|
+
end
|
124
138
|
elsif cust_col
|
125
139
|
data = cust_col.first.map { |cc_part| obj.send(cc_part.last) }
|
126
140
|
cust_txt = klass.brick_descrip(cust_col[-2], data)
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -218,7 +218,8 @@ module Brick
|
|
218
218
|
puts " belongs_to :#{a.name}, polymorphic: true"
|
219
219
|
end
|
220
220
|
else
|
221
|
-
|
221
|
+
bt_key = a.foreign_key.is_a?(Array) ? a.foreign_key : a.foreign_key.to_s
|
222
|
+
s.first[bt_key] = [a.name, a.klass]
|
222
223
|
end
|
223
224
|
else # This gets all forms of has_many and has_one
|
224
225
|
if through # has_many :through or has_one :through
|
@@ -525,7 +526,7 @@ module Brick
|
|
525
526
|
v.each do |type|
|
526
527
|
# Allow polymorphic BT to relate to an STI subclass
|
527
528
|
base_type = ::Brick.config.sti_namespace_prefixes["::#{type}"] ||
|
528
|
-
::Brick.config.sti_namespace_prefixes.find { |k, _v| type.start_with?(k[2..-1]) }&.last&.[](2..-1)
|
529
|
+
::Brick.config.sti_namespace_prefixes.find { |k, _v| k.end_with?('::') && type.start_with?(k[2..-1]) }&.last&.[](2..-1)
|
529
530
|
if relations.key?(primary_table = (base_type || type).underscore.pluralize)
|
530
531
|
::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations,
|
531
532
|
type, # Polymorphic class
|
@@ -605,7 +606,8 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
605
606
|
::Rails.configuration.eager_load_namespaces.select { |ns| ns < ::Rails::Application }.each(&:eager_load!)
|
606
607
|
end
|
607
608
|
else
|
608
|
-
Zeitwerk::Loader.eager_load_all
|
609
|
+
# Same as: Zeitwerk::Loader.eager_load_all -- plus retry when something skips a beat
|
610
|
+
Zeitwerk::Registry.loaders.each { |loader| load_with_retry(loader) }
|
609
611
|
end
|
610
612
|
abstract_ar_bases = if do_ar_abstract_bases
|
611
613
|
ActiveRecord::Base.descendants.select { |ar| ar.abstract_class? }.map(&:name)
|
@@ -614,6 +616,20 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
614
616
|
abstract_ar_bases
|
615
617
|
end
|
616
618
|
|
619
|
+
# Some classes (like Phlex::Testing::Rails) will successfully auto-load after a retry
|
620
|
+
def load_with_retry(loader, autoloaded = nil)
|
621
|
+
autoloaded ||= loader.send(:autoloaded_dirs).dup
|
622
|
+
begin
|
623
|
+
loader.eager_load
|
624
|
+
rescue Zeitwerk::SetupRequired
|
625
|
+
# This is fine -- we eager load what can be eager loaded
|
626
|
+
rescue Zeitwerk::NameError
|
627
|
+
if autoloaded != (new_auto = loader.send(:autoloaded_dirs))
|
628
|
+
load_with_retry(loader, new_auto.dup) # Try one more time and it could come together
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
617
633
|
def display_classes(prefix, rels, max_length)
|
618
634
|
rels.sort.each do |rel|
|
619
635
|
::Brick.auto_models << rel.first
|
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.126
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -244,6 +244,7 @@ files:
|
|
244
244
|
- lib/brick/frameworks/rails/controller.rb
|
245
245
|
- lib/brick/frameworks/rails/crosstab.brk
|
246
246
|
- lib/brick/frameworks/rails/engine.rb
|
247
|
+
- lib/brick/frameworks/rails/form_builder.rb
|
247
248
|
- lib/brick/frameworks/rails/form_tags.rb
|
248
249
|
- lib/brick/frameworks/rspec.rb
|
249
250
|
- lib/brick/join_array.rb
|
@@ -270,7 +271,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
270
271
|
requirements:
|
271
272
|
- - ">="
|
272
273
|
- !ruby/object:Gem::Version
|
273
|
-
version: 2.3.
|
274
|
+
version: 2.3.8
|
274
275
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
275
276
|
requirements:
|
276
277
|
- - ">="
|