brick 1.0.206 → 1.0.207
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +9 -0
- data/lib/brick/extensions.rb +29 -24
- data/lib/brick/frameworks/rails/engine.rb +3 -234
- data/lib/brick/frameworks/rails/form_tags.rb +49 -4
- data/lib/brick/frameworks/rails.rb +199 -0
- data/lib/brick/reflect_tables.rb +3 -3
- data/lib/brick/route_mapper.rb +9 -8
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +18 -9
- data/lib/generators/brick/install_generator.rb +6 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 585c18ee97a6ecead8dec67a3bd9ad7ef765524bb109c72c5550f9f0dbd3b98f
|
4
|
+
data.tar.gz: a0ff5371aef56af539e2022349c0b07c0cdad0bdc93dcbb35aa54a1a8ef3ae4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73f5772ef3b440025b50c62b727a58e63392b7642f326d43e63be64f5c2d68eb9fcc1e8a0d77279f0491a284e51cdeb3ca1db90a67bf2b79d1a4f62371575c71
|
7
|
+
data.tar.gz: 9c068ca26a463307b7f931ca7dcd3c4c12bb06dcc313b284a286aa509ef039b5e4efea1108be9f83839bdb84de47249b9c93a4418dcfc51fe507fd85dba0a6dd
|
data/lib/brick/config.rb
CHANGED
@@ -232,6 +232,15 @@ module Brick
|
|
232
232
|
@mutex.synchronize { @hmts = assocs }
|
233
233
|
end
|
234
234
|
|
235
|
+
# Tables to treat as associative, even when they have data columns
|
236
|
+
def treat_as_associative
|
237
|
+
@mutex.synchronize { @treat_as_associative }
|
238
|
+
end
|
239
|
+
|
240
|
+
def treat_as_associative=(tables)
|
241
|
+
@mutex.synchronize { @treat_as_associative = tables }
|
242
|
+
end
|
243
|
+
|
235
244
|
# Polymorphic associations
|
236
245
|
def polymorphics
|
237
246
|
@mutex.synchronize { @polymorphics ||= {} }
|
data/lib/brick/extensions.rb
CHANGED
@@ -154,6 +154,10 @@ module ActiveRecord
|
|
154
154
|
end
|
155
155
|
dsl
|
156
156
|
end
|
157
|
+
|
158
|
+
def _brick_monetized_attributes
|
159
|
+
@_brick_monetized_attributes ||= respond_to?(:monetized_attributes) ? monetized_attributes.values : {}
|
160
|
+
end
|
157
161
|
end
|
158
162
|
|
159
163
|
def self.brick_parse_dsl(join_array = nil, prefix = [], translations = {}, is_polymorphic = false, dsl = nil, emit_dsl = false)
|
@@ -549,22 +553,16 @@ module ActiveRecord
|
|
549
553
|
# things ... also if there are any HM counts then an OUTER JOIN for each of them out
|
550
554
|
# to a derived table to do that counting. All of these things need to know proper
|
551
555
|
# table correlation names, which will now become available from brick_links on the
|
552
|
-
#
|
556
|
+
# rel_dupe object.)
|
553
557
|
@_brick_links ||= begin
|
554
558
|
# If it's a CollectionProxy (which inherits from Relation) then need to dig
|
555
559
|
# out the core Relation object which is found in the association scope.
|
556
560
|
rel_dupe = (is_a?(ActiveRecord::Associations::CollectionProxy) ? scope : self).dup
|
557
|
-
#
|
561
|
+
# Start out with a hash that has only the root table name
|
558
562
|
rel_dupe.instance_variable_set(:@_brick_links, bl = { '' => table_name })
|
559
|
-
# Walk the AST tree in order to capture all the correlation names
|
560
|
-
rel_dupe.arel.ast
|
561
|
-
# Now that @_brick_links are captured, we can garbage collect the @_brick_rel_dupe object
|
562
|
-
# remove_instance_variable(:@_brick_rel_dupe)
|
563
|
+
rel_dupe.arel.ast # Walk the AST tree in order to capture all the other correlation names
|
563
564
|
bl
|
564
565
|
end
|
565
|
-
# if @_brick_rel_dupe
|
566
|
-
# end
|
567
|
-
# @_brick_links
|
568
566
|
end
|
569
567
|
|
570
568
|
def brick_select(*args, params: {}, order_by: nil, translations: {},
|
@@ -835,7 +833,7 @@ module ActiveRecord
|
|
835
833
|
|
836
834
|
idx = 0
|
837
835
|
bail_out = nil
|
838
|
-
through_sources.map do |a|
|
836
|
+
the_chain = through_sources.map do |a|
|
839
837
|
from_clause << "\n LEFT OUTER JOIN #{a.table_name} br_t#{idx += 1} "
|
840
838
|
from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
|
841
839
|
link_back << (nm = hmt_assoc.source_reflection.inverse_of&.name)
|
@@ -892,7 +890,12 @@ module ActiveRecord
|
|
892
890
|
|
893
891
|
pri_tbl = hm.active_record
|
894
892
|
pri_key = hm.options[:primary_key] || pri_tbl.primary_key
|
895
|
-
if hm.active_record.abstract_class ||
|
893
|
+
if hm.active_record.abstract_class || case pri_key
|
894
|
+
when String
|
895
|
+
hm.active_record.column_names.exclude?(pri_key)
|
896
|
+
when Array
|
897
|
+
(pri_key - hm.active_record.column_names).length > 0
|
898
|
+
end
|
896
899
|
# %%% When this gets hit then if an attempt is made to display the ERD, it might end up being blank
|
897
900
|
nix << k
|
898
901
|
next
|
@@ -908,7 +911,7 @@ module ActiveRecord
|
|
908
911
|
on_clause << "#{_br_quoted_name("#{tbl_alias}.#{fk_col}")} = #{_br_quoted_name("#{pri_tbl.table_name}.#{pri_key}")}"
|
909
912
|
[fk_col]
|
910
913
|
else # Composite key
|
911
|
-
fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_key[idx]}" }
|
914
|
+
fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{_br_quoted_name("#{tbl_alias}.#{fk_col_part}")} = #{_br_quoted_name("#{pri_tbl.table_name}.#{pri_key[idx]}")}" }
|
912
915
|
fk_col.dup
|
913
916
|
end
|
914
917
|
if poly_type
|
@@ -2006,16 +2009,18 @@ class Object
|
|
2006
2009
|
instance_variable_set(:@resources, ::Brick.get_status_of_resources)
|
2007
2010
|
add_csp_hash
|
2008
2011
|
end
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2012
|
+
# # if ::Brick.config.add_schema
|
2013
|
+
# # Currently can only do adding columns
|
2014
|
+
# self.define_method :schema_create do
|
2015
|
+
# if (base_class = (model = params['modelName']&.constantize).base_class) &&
|
2016
|
+
# base_class.column_names.exclude?(col_name = params['colName'])
|
2017
|
+
# ActiveRecord::Base.connection.add_column(base_class.table_name.to_sym, col_name, (col_type = params['colType']).to_sym)
|
2018
|
+
# base_class.reset_column_information
|
2019
|
+
# ::Brick.relations[base_class.table_name]&.fetch(:cols, nil)&.[]=(col_name, [col_type, nil, false, false])
|
2020
|
+
# # instance_variable_set(:@schema, ::Brick.find_schema(::Brick.set_db_schema(params).first))
|
2021
|
+
# add_csp_hash
|
2022
|
+
# end
|
2023
|
+
# end
|
2019
2024
|
self.define_method :orphans do
|
2020
2025
|
instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params).first))
|
2021
2026
|
add_csp_hash
|
@@ -2403,7 +2408,7 @@ class Object
|
|
2403
2408
|
index
|
2404
2409
|
render :index
|
2405
2410
|
else # Surface errors to the user in a flash message
|
2406
|
-
flash.alert = (created_obj.errors.errors.map { |err| "<b>#{err.attribute}</b> #{err.message}" }.join(', '))
|
2411
|
+
flash.now.alert = (created_obj.errors.errors.map { |err| "<b>#{err.attribute}</b> #{err.message}" }.join(', '))
|
2407
2412
|
new
|
2408
2413
|
render :new
|
2409
2414
|
end
|
@@ -2476,7 +2481,7 @@ class Object
|
|
2476
2481
|
end
|
2477
2482
|
obj.send(:update, upd_hash || upd_params)
|
2478
2483
|
if obj.errors.any? # Surface errors to the user in a flash message
|
2479
|
-
flash.alert = (obj.errors.errors.map { |err| "<b>#{err.attribute}</b> #{err.message}" }.join(', '))
|
2484
|
+
flash.now.alert = (obj.errors.errors.map { |err| "<b>#{err.attribute}</b> #{err.message}" }.join(', '))
|
2480
2485
|
end
|
2481
2486
|
end
|
2482
2487
|
|
@@ -2,115 +2,6 @@
|
|
2
2
|
|
3
3
|
module Brick
|
4
4
|
module Rails
|
5
|
-
class << self
|
6
|
-
def display_value(col_type, val, lat_lng = nil)
|
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 && ::Brick.is_geography?(val)
|
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 && ::Brick.is_geography?(val))
|
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
|
-
val = if ((geometry = RGeo::WKRep::WKBParser.new.parse(val.pack('c*'))).is_a?(RGeo::Cartesian::PointImpl) ||
|
50
|
-
geometry.is_a?(RGeo::Geos::CAPIPointImpl)) &&
|
51
|
-
!(geometry.y == 0.0 && geometry.x == 0.0)
|
52
|
-
# 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
|
53
|
-
"<a href=\"https://www.google.com/maps/place/#{geometry.y}+#{geometry.x}/@#{geometry.y},#{geometry.x},12z\" target=\"blank\">#{geometry.to_s}</a>"
|
54
|
-
end
|
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)
|
62
|
-
else
|
63
|
-
if col_type
|
64
|
-
if lat_lng && !(lat_lng.first.zero? && lat_lng.last.zero?)
|
65
|
-
# Create a link to this style of Google maps URL: https://www.google.com/maps/place/38.7071296+-121.2810649/@38.7071296,-121.2810649,12z
|
66
|
-
"<a href=\"https://www.google.com/maps/place/#{lat_lng.first}+#{lat_lng.last}/@#{lat_lng.first},#{lat_lng.last},12z\" target=\"blank\">#{val}</a>"
|
67
|
-
elsif val.is_a?(Numeric) && ::ActiveSupport.const_defined?(:NumberHelper)
|
68
|
-
::ActiveSupport::NumberHelper.number_to_delimited(val, delimiter: ',')
|
69
|
-
else
|
70
|
-
::Brick::Rails::FormBuilder.hide_bcrypt(val, col_type == :xml)
|
71
|
-
end
|
72
|
-
else
|
73
|
-
'?'
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def display_binary(val, max_size = 100_000)
|
79
|
-
return unless val
|
80
|
-
|
81
|
-
@image_signatures ||= { (+"\xFF\xD8\xFF\xEE").force_encoding('ASCII-8BIT') => 'jpeg',
|
82
|
-
(+"\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01").force_encoding('ASCII-8BIT') => 'jpeg',
|
83
|
-
(+"\xFF\xD8\xFF\xDB").force_encoding('ASCII-8BIT') => 'jpeg',
|
84
|
-
(+"\xFF\xD8\xFF\xE1").force_encoding('ASCII-8BIT') => 'jpeg',
|
85
|
-
(+"\x89PNG\r\n\x1A\n").force_encoding('ASCII-8BIT') => 'png',
|
86
|
-
'<svg' => 'svg+xml', # %%% Not yet very good detection for SVG
|
87
|
-
(+'BM').force_encoding('ASCII-8BIT') => 'bmp',
|
88
|
-
(+'GIF87a').force_encoding('ASCII-8BIT') => 'gif',
|
89
|
-
(+'GIF89a').force_encoding('ASCII-8BIT') => 'gif' }
|
90
|
-
|
91
|
-
if val[0..1] == "\x15\x1C" # One of those goofy Microsoft OLE containers?
|
92
|
-
package_header_length = val[2..3].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
93
|
-
# This will often be just FF FF FF FF
|
94
|
-
# object_size = val[16..19].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
95
|
-
friendly_and_class_names = val[20...package_header_length].split("\0")
|
96
|
-
object_type_name_length = val[package_header_length + 8..package_header_length+11].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
97
|
-
friendly_and_class_names << val[package_header_length + 12...package_header_length + 12 + object_type_name_length].strip
|
98
|
-
# friendly_and_class_names will now be something like: ['Bitmap Image', 'Paint.Picture', 'PBrush']
|
99
|
-
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 }
|
100
|
-
object_start = package_header_length + 24 + object_type_name_length
|
101
|
-
val = val[object_start...object_start + real_object_size]
|
102
|
-
end
|
103
|
-
|
104
|
-
if ((signature = @image_signatures.find { |k, _v| val[0...k.length] == k }&.last) ||
|
105
|
-
(val[0..3] == 'RIFF' && val[8..11] == 'WEBP' && binding.local_variable_set(:signature, 'webp'))) &&
|
106
|
-
val.length < max_size
|
107
|
-
"<img src=\"data:image/#{signature.last};base64,#{Base64.encode64(val)}\">"
|
108
|
-
else
|
109
|
-
"< #{signature ? "#{signature} image" : 'Binary'}, #{val.length} bytes >"
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
5
|
# See http://guides.rubyonrails.org/engines.html
|
115
6
|
class Engine < ::Rails::Engine
|
116
7
|
JS_CHANGEOUT = "function changeout(href, param, value, trimAfter) {
|
@@ -209,10 +100,7 @@ function linkSchemas() {
|
|
209
100
|
# paths['app/models'] << 'lib/brick/frameworks/active_record/models'
|
210
101
|
config.brick = ActiveSupport::OrderedOptions.new
|
211
102
|
ActiveSupport.on_load(:before_initialize) do |app|
|
212
|
-
|
213
|
-
# --------------------------------------------
|
214
|
-
# 1. Load three initializers early
|
215
|
-
# (inflectsions.rb, brick.rb, apartment.rb)
|
103
|
+
# Load three initializers early (inflections.rb, brick.rb, apartment.rb)
|
216
104
|
# Very first thing, load inflections since we'll be using .pluralize and .singularize on table and model names
|
217
105
|
if File.exist?(inflections = ::Rails.root&.join('config/initializers/inflections.rb') || '')
|
218
106
|
load inflections
|
@@ -1194,125 +1082,6 @@ if (window.brickFontFamily) {
|
|
1194
1082
|
Apartment::Tenant.switch!(apartment_default_schema)
|
1195
1083
|
end %>"
|
1196
1084
|
|
1197
|
-
erd_markup = if @_brick_model
|
1198
|
-
"<div id=\"mermaidErd\">
|
1199
|
-
<div id=\"mermaidDiagram\" class=\"mermaid\">
|
1200
|
-
erDiagram
|
1201
|
-
<% def sidelinks(shown_classes, klass)
|
1202
|
-
links = []
|
1203
|
-
# %%% Not yet showing these as they can get just a bit intense!
|
1204
|
-
# klass.reflect_on_all_associations.select { |a| shown_classes.key?(a.klass) }.each do |assoc|
|
1205
|
-
# unless shown_classes[assoc.klass].key?(klass.name)
|
1206
|
-
# links << \" #\{klass.name.split('::').last} #\{assoc.macro == :belongs_to ? '}o--||' : '||--o{'} #\{assoc.klass.name.split('::').last} : \\\"\\\"\"n\"
|
1207
|
-
# shown_classes[assoc.klass][klass.name] = nil
|
1208
|
-
# end
|
1209
|
-
# end
|
1210
|
-
# shown_classes[klass] ||= {}
|
1211
|
-
links.join
|
1212
|
-
end
|
1213
|
-
|
1214
|
-
model_short_name = #{@_brick_model.name.split('::').last.inspect}
|
1215
|
-
shown_classes = {}
|
1216
|
-
@_brick_bt_descrip&.each do |bt|
|
1217
|
-
bt_class = bt[1].first.first
|
1218
|
-
callbacks[bt_name = bt_class.name.split('::').last] = bt_class
|
1219
|
-
is_has_one = #{@_brick_model.name}.reflect_on_association(bt.first)&.inverse_of&.macro == :has_one ||
|
1220
|
-
::Brick.config.has_ones&.fetch('#{@_brick_model.name}', nil)&.key?(bt.first.to_s)
|
1221
|
-
%> <%= \"#\{model_short_name} #\{is_has_one ? '||' : '}o'}--|| #\{bt_name} : \\\"#\{
|
1222
|
-
bt_underscored = bt[1].first.first.name.underscore.singularize
|
1223
|
-
bt.first unless bt.first.to_s == bt_underscored.split('/').last # Was: bt_underscored.tr('/', '_')
|
1224
|
-
}\\\"\".html_safe %>
|
1225
|
-
<%= sidelinks(shown_classes, bt_class).html_safe %>
|
1226
|
-
<% end
|
1227
|
-
last_hm = nil
|
1228
|
-
@_brick_hm_counts&.each do |hm|
|
1229
|
-
# Skip showing self-referencing HM links since they would have already been drawn while evaluating the BT side
|
1230
|
-
next if (hm_class = hm.last&.klass) == #{@_brick_model.name}
|
1231
|
-
|
1232
|
-
callbacks[hm_name = hm_class.name.split('::').last] = hm_class
|
1233
|
-
if (through = hm.last.options[:through]&.to_s) # has_many :through (HMT)
|
1234
|
-
through_name = (through_assoc = hm.last.source_reflection).active_record.name.split('::').last
|
1235
|
-
callbacks[through_name] = through_assoc.active_record
|
1236
|
-
if last_hm == through # Same HM, so no need to build it again, and for clarity just put in a blank line
|
1237
|
-
%><%= \"\n\"
|
1238
|
-
%><% else
|
1239
|
-
%> <%= \"#\{model_short_name} ||--o{ #\{through_name}\".html_safe %> : \"\"
|
1240
|
-
<%= sidelinks(shown_classes, through_assoc.active_record).html_safe %>
|
1241
|
-
<% last_hm = through
|
1242
|
-
end
|
1243
|
-
%> <%= \"#\{through_name} }o--|| #\{hm_name}\".html_safe %> : \"\"
|
1244
|
-
<%= \"#\{model_short_name} }o..o{ #\{hm_name} : \\\"#\{hm.first}\\\"\".html_safe %><%
|
1245
|
-
else # has_many
|
1246
|
-
%> <%= \"#\{model_short_name} ||--o{ #\{hm_name} : \\\"#\{
|
1247
|
-
hm.first.to_s unless (last_hm = hm.first.to_s).downcase == hm_class.name.underscore.pluralize.tr('/', '_')
|
1248
|
-
}\\\"\".html_safe %><%
|
1249
|
-
end %>
|
1250
|
-
<%= sidelinks(shown_classes, hm_class).html_safe %>
|
1251
|
-
<% end
|
1252
|
-
def dt_lookup(dt)
|
1253
|
-
{ 'integer' => 'int', }[dt] || dt&.tr(' ', '_') || 'int'
|
1254
|
-
end
|
1255
|
-
callbacks.merge({model_short_name => #{@_brick_model.name}}).each do |cb_k, cb_class|
|
1256
|
-
cb_relation = ::Brick.relations[cb_class.table_name]
|
1257
|
-
pkeys = cb_relation[:pkey]&.first&.last
|
1258
|
-
fkeys = cb_relation[:fks]&.values&.each_with_object([]) { |fk, s| s << fk[:fk] if fk.fetch(:is_bt, nil) }
|
1259
|
-
cols = cb_relation[:cols]
|
1260
|
-
%> <%= cb_k %> {<%
|
1261
|
-
pkeys&.each do |pk| %>
|
1262
|
-
<%= \"#\{dt_lookup(cols[pk].first)} #\{pk} \\\"PK#\{' fk' if fkeys&.include?(pk)}\\\"\".html_safe %><%
|
1263
|
-
end %><%
|
1264
|
-
fkeys&.each do |fk|
|
1265
|
-
if fk.is_a?(Array)
|
1266
|
-
fk.each do |fk_part| %>
|
1267
|
-
<%= \"#\{dt_lookup(cols[fk_part].first)} #\{fk_part} \\\" fk\\\"\".html_safe unless pkeys&.include?(fk_part) %><%
|
1268
|
-
end
|
1269
|
-
else # %%% Does not yet accommodate polymorphic BTs
|
1270
|
-
%>
|
1271
|
-
<%= \"#\{dt_lookup(cols[fk]&.first)} #\{fk} \\\" fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
|
1272
|
-
end
|
1273
|
-
end %>
|
1274
|
-
}
|
1275
|
-
<% end
|
1276
|
-
# callback < %= cb_k % > erdClick
|
1277
|
-
@_brick_monetized_attributes = model.respond_to?(:monetized_attributes) ? model.monetized_attributes.values : {}
|
1278
|
-
%>
|
1279
|
-
</div>#{
|
1280
|
-
add_column = nil
|
1281
|
-
# Make into a server control with a javascript snippet
|
1282
|
-
# Have post back go to a common "brick_schema" endpoint, this one for add_column
|
1283
|
-
"
|
1284
|
-
<table id=\"tblAddCol\"><tr>
|
1285
|
-
<td rowspan=\"2\">Add<br>Column</td>
|
1286
|
-
<td class=\"paddingBottomZero\">Type</td><td class=\"paddingBottomZero\">Name</td>
|
1287
|
-
<td rowspan=\"2\"><input type=\"button\" id=\"btnAddCol\" value=\"+\"></td>
|
1288
|
-
</tr><tr><td class=\"paddingTopZero\">
|
1289
|
-
<select id=\"ddlColType\">
|
1290
|
-
<option value=\"string\">String</option>
|
1291
|
-
<option value=\"text\">Text</option>
|
1292
|
-
<option value=\"integer\">Integer</option>
|
1293
|
-
<option value=\"bool\">Boolean</option>
|
1294
|
-
</select></td>
|
1295
|
-
<td class=\"paddingTopZero\"><input id=\"txtColName\"></td>
|
1296
|
-
</tr></table>
|
1297
|
-
<script>
|
1298
|
-
var btnAddCol = document.getElementById(\"btnAddCol\");
|
1299
|
-
btnAddCol.addEventListener(\"click\", function () {
|
1300
|
-
var txtColName = document.getElementById(\"txtColName\");
|
1301
|
-
var ddlColType = document.getElementById(\"ddlColType\");
|
1302
|
-
doFetch(\"POST\", {modelName: \"#{@_brick_model.name}\",
|
1303
|
-
colName: txtColName.value, colType: ddlColType.value,
|
1304
|
-
_brick_action: \"/#{prefix}brick_schema\"},
|
1305
|
-
function () { // If it returns successfully, do a page refresh
|
1306
|
-
location.href = location.href;
|
1307
|
-
}
|
1308
|
-
);
|
1309
|
-
});
|
1310
|
-
</script>
|
1311
|
-
" unless add_column == false}
|
1312
|
-
|
1313
|
-
</div>
|
1314
|
-
"
|
1315
|
-
end
|
1316
1085
|
inline = case args.first
|
1317
1086
|
when 'index'
|
1318
1087
|
if Object.const_defined?('DutyFree')
|
@@ -1506,7 +1275,7 @@ end
|
|
1506
1275
|
</script>
|
1507
1276
|
<% end %>
|
1508
1277
|
</div></div>
|
1509
|
-
#{erd_markup}
|
1278
|
+
#{::Brick::Rails.erd_markup(@_brick_model, prefix) if @_brick_model}
|
1510
1279
|
|
1511
1280
|
<%= # Consider getting the name from the association -- hm.first.name -- if a more \"friendly\" alias should be used for a screwy table name
|
1512
1281
|
# If the resource is missing, has the user simply created an inappropriately pluralised name for a table?
|
@@ -1714,7 +1483,7 @@ if (description = rel&.fetch(:description, nil)) %>
|
|
1714
1483
|
<span class=\"__brick\"><%= description %></span><br><%
|
1715
1484
|
end
|
1716
1485
|
%><%= link_to \"(See all #\{model_name.pluralize})\", see_all_path, { class: '__brick' } %>
|
1717
|
-
#{erd_markup}
|
1486
|
+
#{::Brick::Rails.erd_markup(@_brick_model, prefix) if @_brick_model}
|
1718
1487
|
<% if obj
|
1719
1488
|
# path_options = [obj.#{pk}]
|
1720
1489
|
# path_options << { '_brick_schema': } if
|
@@ -230,7 +230,7 @@ module Brick::Rails::FormTags
|
|
230
230
|
end
|
231
231
|
elsif (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
|
232
232
|
# binding.pry if col.is_a?(Array)
|
233
|
-
out << if
|
233
|
+
out << if klass._brick_monetized_attributes&.include?(col_name)
|
234
234
|
val ? Money.new(val.to_i).format : ''
|
235
235
|
elsif klass.respond_to?(:uploaders) && klass.uploaders.key?(col_name.to_sym) &&
|
236
236
|
(url = obj.send(col_name)&.url) # Carrierwave image?
|
@@ -424,6 +424,7 @@ function onImagesLoaded(event) {
|
|
424
424
|
out.html_safe
|
425
425
|
end # brick_grid
|
426
426
|
|
427
|
+
# -----------------------------
|
427
428
|
# Our mega show/new/update form
|
428
429
|
def brick_form_for(obj, options = {}, model = obj.class, bts = {}, pk = (obj.class.primary_key || []))
|
429
430
|
pk = [pk] unless pk.is_a?(Array)
|
@@ -439,7 +440,8 @@ function onImagesLoaded(event) {
|
|
439
440
|
end if obj.new_record?
|
440
441
|
rtans = model.rich_text_association_names if model.respond_to?(:rich_text_association_names)
|
441
442
|
(model.column_names + (rtans || [])).each do |k|
|
442
|
-
|
443
|
+
pk_pos = (pk.index(k)&.+ 1)
|
444
|
+
next if (pk_pos && pk.length == 1 && !bts.key?(k)) ||
|
443
445
|
::Brick.config.metadata_columns.include?(k)
|
444
446
|
|
445
447
|
col = model.columns_hash[k]
|
@@ -452,7 +454,7 @@ function onImagesLoaded(event) {
|
|
452
454
|
end
|
453
455
|
val = obj.attributes[k]
|
454
456
|
out << "
|
455
|
-
|
457
|
+
<tr>
|
456
458
|
<th class=\"show-field\"#{" title=\"#{col&.comment}\"".html_safe if col&.respond_to?(:comment) && !col&.comment.blank?}>"
|
457
459
|
has_fields = true
|
458
460
|
if (bt = bts[k])
|
@@ -496,10 +498,17 @@ function onImagesLoaded(event) {
|
|
496
498
|
else
|
497
499
|
out << model.human_attribute_name(k, { default: k })
|
498
500
|
end
|
501
|
+
out << " (PK #{pk_pos})" if pk_pos
|
499
502
|
out << "
|
500
503
|
</th>
|
501
504
|
<td>
|
502
|
-
|
505
|
+
"
|
506
|
+
if pk_pos
|
507
|
+
out << val.to_s
|
508
|
+
else
|
509
|
+
out << f.brick_field(k, html_options = {}, val, col, bt, bt_class, bt_name, bt_pair)
|
510
|
+
end
|
511
|
+
out << "
|
503
512
|
</td>
|
504
513
|
</tr>"
|
505
514
|
end
|
@@ -519,6 +528,7 @@ function onImagesLoaded(event) {
|
|
519
528
|
end
|
520
529
|
end # brick_form_for
|
521
530
|
|
531
|
+
# --------------------------------
|
522
532
|
def link_to_brick(*args, **kwargs)
|
523
533
|
return unless ::Brick.config.mode == :on
|
524
534
|
|
@@ -616,6 +626,41 @@ function onImagesLoaded(event) {
|
|
616
626
|
end
|
617
627
|
end # link_to_brick
|
618
628
|
|
629
|
+
# ---------------------------------
|
630
|
+
def brick_add_column(model, prefix)
|
631
|
+
# TODO: Make a server control architecture that has separate javascript snippets
|
632
|
+
# Have post back go to a common "brick_schema" endpoint, this one for add_column
|
633
|
+
"
|
634
|
+
<table id=\"tblAddCol\"><tr>
|
635
|
+
<td rowspan=\"2\">Add<br>Column</td>
|
636
|
+
<td class=\"paddingBottomZero\">Type</td><td class=\"paddingBottomZero\">Name</td>
|
637
|
+
<td rowspan=\"2\"><input type=\"button\" id=\"btnAddCol\" value=\"+\"></td>
|
638
|
+
</tr><tr><td class=\"paddingTopZero\">
|
639
|
+
<select id=\"ddlColType\">
|
640
|
+
<option value=\"string\">String</option>
|
641
|
+
<option value=\"text\">Text</option>
|
642
|
+
<option value=\"integer\">Integer</option>
|
643
|
+
<option value=\"bool\">Boolean</option>
|
644
|
+
</select></td>
|
645
|
+
<td class=\"paddingTopZero\"><input id=\"txtColName\"></td>
|
646
|
+
</tr></table>
|
647
|
+
<script>
|
648
|
+
var btnAddCol = document.getElementById(\"btnAddCol\");
|
649
|
+
btnAddCol.addEventListener(\"click\", function () {
|
650
|
+
var txtColName = document.getElementById(\"txtColName\");
|
651
|
+
var ddlColType = document.getElementById(\"ddlColType\");
|
652
|
+
doFetch(\"POST\", {modelName: \"#{model.name}\",
|
653
|
+
colName: txtColName.value, colType: ddlColType.value,
|
654
|
+
_brick_action: \"/#{prefix}brick_schema\"},
|
655
|
+
function () { // If it returns successfully, do a page refresh
|
656
|
+
location.href = location.href;
|
657
|
+
}
|
658
|
+
);
|
659
|
+
});
|
660
|
+
</script>
|
661
|
+
"
|
662
|
+
end
|
663
|
+
|
619
664
|
private
|
620
665
|
|
621
666
|
def _brick_resource_from_iv(trim_ampersand = false)
|
@@ -4,6 +4,205 @@
|
|
4
4
|
require 'brick/frameworks/rails/engine'
|
5
5
|
|
6
6
|
module ::Brick::Rails
|
7
|
+
class << self
|
8
|
+
def display_value(col_type, val, lat_lng = nil)
|
9
|
+
is_mssql_geography = nil
|
10
|
+
# 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)
|
11
|
+
if col_type == :binary && val && ::Brick.is_geography?(val)
|
12
|
+
col_type = 'geography'
|
13
|
+
is_mssql_geography = true
|
14
|
+
end
|
15
|
+
case col_type
|
16
|
+
when 'geometry', 'geography'
|
17
|
+
if Object.const_defined?('RGeo')
|
18
|
+
@is_mysql = ['Mysql2', 'Trilogy'].include?(ActiveRecord::Base.connection.adapter_name) if @is_mysql.nil?
|
19
|
+
@is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if @is_mssql.nil?
|
20
|
+
val_err = nil
|
21
|
+
|
22
|
+
if @is_mysql || (is_mssql_geography ||=
|
23
|
+
(@is_mssql ||
|
24
|
+
(val && ::Brick.is_geography?(val))
|
25
|
+
)
|
26
|
+
)
|
27
|
+
# MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
|
28
|
+
if (srid = val&.[](0..3)&.unpack('I'))
|
29
|
+
val = val.dup.force_encoding('BINARY')[4..-1].bytes
|
30
|
+
|
31
|
+
# MSSQL spatial bitwise flags, often 0C for a point:
|
32
|
+
# xxxx xxx1 = HasZValues
|
33
|
+
# xxxx xx1x = HasMValues
|
34
|
+
# xxxx x1xx = IsValid
|
35
|
+
# xxxx 1xxx = IsSinglePoint
|
36
|
+
# xxx1 xxxx = IsSingleLineSegment
|
37
|
+
# xx1x xxxx = IsWholeGlobe
|
38
|
+
# Convert Microsoft's unique geography binary to standard WKB
|
39
|
+
# (MSSQL point usually has two doubles, lng / lat, and can also have Z)
|
40
|
+
if is_mssql_geography
|
41
|
+
if val[0] == 1 && (val[1] & 8 > 0) && # Single point?
|
42
|
+
(val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
|
43
|
+
val = [0, 0, 0, 0, 1] + val[2..-1].reverse
|
44
|
+
else
|
45
|
+
val_err = '(Microsoft internal SQL geography type)'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
unless val_err || val.nil?
|
51
|
+
val = if ((geometry = RGeo::WKRep::WKBParser.new.parse(val.pack('c*'))).is_a?(RGeo::Cartesian::PointImpl) ||
|
52
|
+
geometry.is_a?(RGeo::Geos::CAPIPointImpl)) &&
|
53
|
+
!(geometry.y == 0.0 && geometry.x == 0.0)
|
54
|
+
# 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
|
55
|
+
"<a href=\"https://www.google.com/maps/place/#{geometry.y}+#{geometry.x}/@#{geometry.y},#{geometry.x},12z\" target=\"blank\">#{geometry.to_s}</a>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
val_err || val
|
59
|
+
else
|
60
|
+
'(Add RGeo gem to parse geometry detail)'
|
61
|
+
end
|
62
|
+
when :binary
|
63
|
+
::Brick::Rails.display_binary(val)
|
64
|
+
else
|
65
|
+
if col_type
|
66
|
+
if lat_lng && !(lat_lng.first.zero? && lat_lng.last.zero?)
|
67
|
+
# Create a link to this style of Google maps URL: https://www.google.com/maps/place/38.7071296+-121.2810649/@38.7071296,-121.2810649,12z
|
68
|
+
"<a href=\"https://www.google.com/maps/place/#{lat_lng.first}+#{lat_lng.last}/@#{lat_lng.first},#{lat_lng.last},12z\" target=\"blank\">#{val}</a>"
|
69
|
+
elsif val.is_a?(Numeric) && ::ActiveSupport.const_defined?(:NumberHelper)
|
70
|
+
::ActiveSupport::NumberHelper.number_to_delimited(val, delimiter: ',')
|
71
|
+
else
|
72
|
+
::Brick::Rails::FormBuilder.hide_bcrypt(val, col_type == :xml)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
'?'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def display_binary(val, max_size = 100_000)
|
81
|
+
return unless val
|
82
|
+
|
83
|
+
@image_signatures ||= { (+"\xFF\xD8\xFF\xEE").force_encoding('ASCII-8BIT') => 'jpeg',
|
84
|
+
(+"\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01").force_encoding('ASCII-8BIT') => 'jpeg',
|
85
|
+
(+"\xFF\xD8\xFF\xDB").force_encoding('ASCII-8BIT') => 'jpeg',
|
86
|
+
(+"\xFF\xD8\xFF\xE1").force_encoding('ASCII-8BIT') => 'jpeg',
|
87
|
+
(+"\x89PNG\r\n\x1A\n").force_encoding('ASCII-8BIT') => 'png',
|
88
|
+
'<svg' => 'svg+xml', # %%% Not yet very good detection for SVG
|
89
|
+
(+'BM').force_encoding('ASCII-8BIT') => 'bmp',
|
90
|
+
(+'GIF87a').force_encoding('ASCII-8BIT') => 'gif',
|
91
|
+
(+'GIF89a').force_encoding('ASCII-8BIT') => 'gif' }
|
92
|
+
|
93
|
+
if val[0..1] == "\x15\x1C" # One of those goofy Microsoft OLE containers?
|
94
|
+
package_header_length = val[2..3].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
95
|
+
# This will often be just FF FF FF FF
|
96
|
+
# object_size = val[16..19].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
97
|
+
friendly_and_class_names = val[20...package_header_length].split("\0")
|
98
|
+
object_type_name_length = val[package_header_length + 8..package_header_length+11].bytes.reverse.inject(0) {|m, b| (m << 8) + b }
|
99
|
+
friendly_and_class_names << val[package_header_length + 12...package_header_length + 12 + object_type_name_length].strip
|
100
|
+
# friendly_and_class_names will now be something like: ['Bitmap Image', 'Paint.Picture', 'PBrush']
|
101
|
+
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 }
|
102
|
+
object_start = package_header_length + 24 + object_type_name_length
|
103
|
+
val = val[object_start...object_start + real_object_size]
|
104
|
+
end
|
105
|
+
|
106
|
+
if ((signature = @image_signatures.find { |k, _v| val[0...k.length] == k }&.last) ||
|
107
|
+
(val[0..3] == 'RIFF' && val[8..11] == 'WEBP' && binding.local_variable_set(:signature, 'webp'))) &&
|
108
|
+
val.length < max_size
|
109
|
+
"<img src=\"data:image/#{signature.last};base64,#{Base64.encode64(val)}\">"
|
110
|
+
else
|
111
|
+
"< #{signature ? "#{signature} image" : 'Binary'}, #{val.length} bytes >"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Generate MermaidJS markup to create a partial ERD for this model
|
116
|
+
def erd_markup(model, prefix)
|
117
|
+
model_short_name = model.name.split('::').last
|
118
|
+
"<div id=\"mermaidErd\">
|
119
|
+
<div id=\"mermaidDiagram\" class=\"mermaid\">
|
120
|
+
erDiagram
|
121
|
+
<% shown_classes = {}
|
122
|
+
|
123
|
+
def erd_sidelinks(shown_classes, klass)
|
124
|
+
links = []
|
125
|
+
# %%% Not yet showing these as they can get just a bit intense!
|
126
|
+
# klass.reflect_on_all_associations.select { |a| shown_classes.key?(a.klass) }.each do |assoc|
|
127
|
+
# unless shown_classes[assoc.klass].key?(klass.name)
|
128
|
+
# links << \" #\{klass.name.split('::').last} #\{assoc.macro == :belongs_to ? '}o--||' : '||--o{'} #\{assoc.klass.name.split('::').last} : \\\"\\\"\"n\"
|
129
|
+
# shown_classes[assoc.klass][klass.name] = nil
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
# shown_classes[klass] ||= {}
|
133
|
+
links.join
|
134
|
+
end
|
135
|
+
|
136
|
+
@_brick_bt_descrip&.each do |bt|
|
137
|
+
bt_class = bt[1].first.first
|
138
|
+
callbacks[bt_name = bt_class.name.split('::').last] = bt_class
|
139
|
+
is_has_one = #{model.name}.reflect_on_association(bt.first)&.inverse_of&.macro == :has_one ||
|
140
|
+
::Brick.config.has_ones&.fetch('#{model.name}', nil)&.key?(bt.first.to_s)
|
141
|
+
%> <%= \"#{model_short_name} #\{is_has_one ? '||' : '}o'}--|| #\{bt_name} : \\\"#\{
|
142
|
+
bt_underscored = bt[1].first.first.name.underscore.singularize
|
143
|
+
bt.first unless bt.first.to_s == bt_underscored.split('/').last # Was: bt_underscored.tr('/', '_')
|
144
|
+
}\\\"\".html_safe %>
|
145
|
+
<%= erd_sidelinks(shown_classes, bt_class).html_safe %>
|
146
|
+
<% end
|
147
|
+
last_hm = nil
|
148
|
+
@_brick_hm_counts&.each do |hm|
|
149
|
+
# Skip showing self-referencing HM links since they would have already been drawn while evaluating the BT side
|
150
|
+
next if (hm_class = hm.last&.klass) == #{model.name}
|
151
|
+
|
152
|
+
callbacks[hm_name = hm_class.name.split('::').last] = hm_class
|
153
|
+
if (through = hm.last.options[:through]&.to_s) # has_many :through (HMT)
|
154
|
+
through_name = (through_assoc = hm.last.source_reflection).active_record.name.split('::').last
|
155
|
+
callbacks[through_name] = through_assoc.active_record
|
156
|
+
if last_hm == through # Same HM, so no need to build it again, and for clarity just put in a blank line
|
157
|
+
%><%= \"\n\"
|
158
|
+
%><% else
|
159
|
+
%> <%= \"#{model_short_name} ||--o{ #\{through_name}\".html_safe %> : \"\"
|
160
|
+
<%= erd_sidelinks(shown_classes, through_assoc.active_record).html_safe %>
|
161
|
+
<% last_hm = through
|
162
|
+
end
|
163
|
+
%> <%= \"#\{through_name} }o--|| #\{hm_name}\".html_safe %> : \"\"
|
164
|
+
<%= \"#{model_short_name} }o..o{ #\{hm_name} : \\\"#\{hm.first}\\\"\".html_safe %><%
|
165
|
+
else # has_many
|
166
|
+
%> <%= \"#{model_short_name} ||--o{ #\{hm_name} : \\\"#\{
|
167
|
+
hm.first.to_s unless (last_hm = hm.first.to_s).downcase == hm_class.name.underscore.pluralize.tr('/', '_')
|
168
|
+
}\\\"\".html_safe %><%
|
169
|
+
end %>
|
170
|
+
<%= erd_sidelinks(shown_classes, hm_class).html_safe %>
|
171
|
+
<% end
|
172
|
+
def dt_lookup(dt)
|
173
|
+
{ 'integer' => 'int', }[dt] || dt&.tr(' ', '_') || 'int'
|
174
|
+
end
|
175
|
+
callbacks.merge({#{model_short_name} => #{model.name}}).each do |cb_k, cb_class|
|
176
|
+
cb_relation = ::Brick.relations[cb_class.table_name]
|
177
|
+
pkeys = cb_relation[:pkey]&.first&.last
|
178
|
+
fkeys = cb_relation[:fks]&.values&.each_with_object([]) { |fk, s| s << fk[:fk] if fk.fetch(:is_bt, nil) }
|
179
|
+
cols = cb_relation[:cols]
|
180
|
+
%> <%= cb_k %> {<%
|
181
|
+
pkeys&.each do |pk| %>
|
182
|
+
<%= \"#\{dt_lookup(cols[pk].first)} #\{pk} \\\"PK#\{' fk' if fkeys&.include?(pk)}\\\"\".html_safe %><%
|
183
|
+
end %><%
|
184
|
+
fkeys&.each do |fk|
|
185
|
+
if fk.is_a?(Array)
|
186
|
+
fk.each do |fk_part| %>
|
187
|
+
<%= \"#\{dt_lookup(cols[fk_part].first)} #\{fk_part} \\\" fk\\\"\".html_safe unless pkeys&.include?(fk_part) %><%
|
188
|
+
end
|
189
|
+
else # %%% Does not yet accommodate polymorphic BTs
|
190
|
+
%>
|
191
|
+
<%= \"#\{dt_lookup(cols[fk]&.first)} #\{fk} \\\" fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
|
192
|
+
end
|
193
|
+
end %>
|
194
|
+
}
|
195
|
+
<% end
|
196
|
+
# callback < %= cb_k % > erdClick
|
197
|
+
%>
|
198
|
+
</div>#{
|
199
|
+
add_column = false # For the moment, disable all schema modification things
|
200
|
+
"<%= brick_add_column(#{model.name}, #{prefix.inspect}).html_safe %>" unless add_column == false}
|
201
|
+
</div>
|
202
|
+
"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
7
206
|
AVO_SVG = "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 84 90\" height=\"30\" fill=\"#3096F7\">
|
8
207
|
<path d=\"M83.8304 81.0201C83.8343 82.9343 83.2216 84.7996 82.0822 86.3423C80.9427 87.8851 79.3363 89.0244 77.4984 89.5931C75.6606 90.1618 73.6878 90.1302 71.8694 89.5027C70.0509 88.8753 68.4823 87.6851 67.3935 86.1065L67.0796 85.6029C66.9412 85.378 66.8146 85.1463 66.6998 84.9079L66.8821 85.3007C64.1347 81.223 60.419 77.8817 56.0639 75.5723C51.7087 73.263 46.8484 72.057 41.9129 72.0609C31.75 72.0609 22.372 77.6459 16.9336 85.336C17.1412 84.7518 17.7185 83.6137 17.9463 83.0446L19.1059 80.5265L19.1414 80.456C25.2533 68.3694 37.7252 59.9541 52.0555 59.9541C53.1949 59.9541 54.3241 60.0095 55.433 60.1102C60.748 60.6134 65.8887 62.2627 70.4974 64.9433C75.1061 67.6238 79.0719 71.2712 82.1188 75.6314C82.1188 75.6314 82.1441 75.6717 82.1593 75.6868C82.1808 75.717 82.1995 75.749 82.215 75.7825C82.2821 75.8717 82.3446 75.9641 82.4024 76.0595C82.4682 76.1653 82.534 76.4221 82.5999 76.5279C82.6657 76.6336 82.772 76.82 82.848 76.9711L83.1822 77.7063C83.6094 78.7595 83.8294 79.8844 83.8304 81.0201V81.0201Z\" fill=\"currentColor\" fill-opacity=\"0.22\"></path>
|
9
208
|
<path opacity=\"0.25\" d=\"M83.8303 81.015C83.8354 82.9297 83.2235 84.7956 82.0844 86.3393C80.9453 87.8829 79.339 89.0229 77.5008 89.5923C75.6627 90.1617 73.6895 90.1304 71.8706 89.5031C70.0516 88.8758 68.4826 87.6854 67.3935 86.1065L67.0796 85.6029C66.9412 85.3746 66.8146 85.1429 66.6998 84.9079L66.8821 85.3007C64.1353 81.222 60.4199 77.8797 56.0647 75.5695C51.7095 73.2593 46.8488 72.0524 41.9129 72.0558C31.75 72.0558 22.372 77.6408 16.9336 85.3309C17.1412 84.7467 17.7185 83.6086 17.9463 83.0395L19.1059 80.5214L19.1414 80.4509C22.1906 74.357 26.8837 69.2264 32.6961 65.6326C38.5086 62.0387 45.2114 60.1232 52.0555 60.1001C53.1949 60.1001 54.3241 60.1555 55.433 60.2562C60.7479 60.7594 65.8887 62.4087 70.4974 65.0893C75.1061 67.7698 79.0719 71.4172 82.1188 75.7775C82.1188 75.7775 82.1441 75.8177 82.1593 75.8328C82.1808 75.863 82.1995 75.895 82.215 75.9285C82.2821 76.0177 82.3446 76.1101 82.4024 76.2055L82.5999 76.5228C82.6859 76.6638 82.772 76.8149 82.848 76.966L83.1822 77.7012C83.6093 78.7544 83.8294 79.8793 83.8303 81.015Z\" fill=\"currentColor\" fill-opacity=\"0.22\"></path>
|
data/lib/brick/reflect_tables.rb
CHANGED
@@ -37,7 +37,7 @@ module Brick
|
|
37
37
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
38
38
|
|
39
39
|
# ---------------------------
|
40
|
-
#
|
40
|
+
# 1. Figure out schema things
|
41
41
|
is_postgres = nil
|
42
42
|
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
|
43
43
|
case ActiveRecord::Base.connection.adapter_name
|
@@ -111,7 +111,7 @@ module Brick
|
|
111
111
|
::Brick.db_schemas ||= {}
|
112
112
|
|
113
113
|
# ---------------------
|
114
|
-
#
|
114
|
+
# 2. Tables and columns
|
115
115
|
# %%% Retrieve internal ActiveRecord table names like this:
|
116
116
|
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
117
117
|
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
@@ -243,7 +243,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
243
243
|
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
244
244
|
|
245
245
|
# ---------------------------------------------
|
246
|
-
#
|
246
|
+
# 3. Foreign key info
|
247
247
|
# (done in two parts which get JOINed together in Ruby code)
|
248
248
|
kcus = nil
|
249
249
|
entry_type = nil
|
data/lib/brick/route_mapper.rb
CHANGED
@@ -277,14 +277,15 @@ module Brick
|
|
277
277
|
get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: status_as.to_s)
|
278
278
|
end
|
279
279
|
|
280
|
-
# ::Brick.config.add_schema &&
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
280
|
+
# # ::Brick.config.add_schema &&
|
281
|
+
# # Currently can only do adding columns
|
282
|
+
# if (schema_as = "#{controller_prefix.tr('/', '_')}brick_schema".to_sym)
|
283
|
+
# (
|
284
|
+
# !(schema_route = instance_variable_get(:@set).named_routes.find { |route| route.first == schema_as }&.last) ||
|
285
|
+
# !schema_route.ast.to_s.include?("/#{controller_prefix}brick_schema/")
|
286
|
+
# )
|
287
|
+
# post("/#{controller_prefix}brick_schema", to: 'brick_gem#schema_create', as: schema_as.to_s)
|
288
|
+
# end
|
288
289
|
|
289
290
|
if ::Brick.config.add_orphans && (orphans_as = "#{controller_prefix.tr('/', '_')}brick_orphans".to_sym)
|
290
291
|
(
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -565,6 +565,12 @@ module Brick
|
|
565
565
|
Brick.config.hmts = assocs
|
566
566
|
end
|
567
567
|
|
568
|
+
# Tables to treat as associative, even when they have data columns
|
569
|
+
# @api public
|
570
|
+
def treat_as_associative=(tables)
|
571
|
+
Brick.config.treat_as_associative = tables
|
572
|
+
end
|
573
|
+
|
568
574
|
# Polymorphic associations
|
569
575
|
def polymorphics=(polys)
|
570
576
|
polys = polys.each_with_object({}) { |poly, s| s[poly] = nil } if polys.is_a?(Array)
|
@@ -674,15 +680,18 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
674
680
|
end
|
675
681
|
|
676
682
|
# Find associative tables that can be set up for has_many :through
|
677
|
-
::Brick.relations.each do |
|
678
|
-
next if
|
679
|
-
|
680
|
-
tbl_cols =
|
681
|
-
fks =
|
682
|
-
# Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table
|
683
|
-
# foreign keys then it can act as an associative table and thus be used with has_many :through.
|
684
|
-
if fks.length > 1 && (
|
685
|
-
|
683
|
+
::Brick.relations.each do |tbl_name, relation|
|
684
|
+
next if tbl_name.is_a?(Symbol)
|
685
|
+
|
686
|
+
tbl_cols = relation[:cols].keys
|
687
|
+
fks = relation[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = [fk.last[:assoc_name], fk.last[:inverse_table]] if fk.last[:is_bt]; s }
|
688
|
+
# Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table
|
689
|
+
# only has foreign keys then it can act as an associative table and thus be used with has_many :through.
|
690
|
+
if fks.length > 1 && (
|
691
|
+
::Brick.config.treat_as_associative&.include?(tbl_name) ||
|
692
|
+
(tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - (relation[:pkey].values.first || [])).length.zero?
|
693
|
+
)
|
694
|
+
fks.each { |fk| relation[:hmt_fks][fk.first] = fk.last }
|
686
695
|
end
|
687
696
|
end
|
688
697
|
end
|
@@ -304,6 +304,12 @@ if ActiveRecord::Base.respond_to?(:brick_select) && !::Brick.initializer_loaded
|
|
304
304
|
# # Auto-create specific has_many ___, through: ___ associations
|
305
305
|
# Brick.hmts = [['recipes', 'recipe_ingredients', 'ingredients']]
|
306
306
|
|
307
|
+
# # Treat specific tables as being associative, using them to wire up HMT relationships. (This is normally the
|
308
|
+
# # default when a table contains only foreign keys, but when that otherwise associative \"JOIN\" table has any
|
309
|
+
# # other data columns, it is considered a data table and not really associative. This overrides in order to
|
310
|
+
# # have a table to still be treated as associative, causing HMTs to be auto-generated.)
|
311
|
+
# Brick.treat_as_associative = ['flights']
|
312
|
+
|
307
313
|
# # We normally don't show the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\", and also do
|
308
314
|
# # not consider them when finding associative tables to support an N:M association. (That is, ones that can be a
|
309
315
|
# # part of a has_many :through association.) If you want to use different exclusion columns than our defaults
|
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.207
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|