brick 1.0.124 → 1.0.126
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - ">="
|