brick 1.0.72 → 1.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +25 -0
- data/lib/brick/extensions.rb +186 -129
- data/lib/brick/frameworks/rails/engine.rb +57 -26
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +83 -24
- data/lib/generators/brick/install_generator.rb +2 -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: a7f077a36e2fd62546cdebfde2b9651807ceeaad9571008460423d9e994df44d
|
4
|
+
data.tar.gz: 368ca31e726fa377325b7ff7838e38cd6b79aa222bcec73cdd6cebc645908d28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4154837ca3926cb1cd8d4803d62a789f1aa6449625560c63c06708508543e218765288d13a763aab238ea946dfdc33391454793b02805a8cdf46f729cf436da
|
7
|
+
data.tar.gz: 599cd989ef47a99cb0b6b4cb1a1c53da892d5758ccd208ecb4bf1383a2f29c1bc4600458c4de357e5496f90f664984c10634f469a40f80bcc89a9e5592735899
|
data/lib/brick/config.rb
CHANGED
@@ -56,6 +56,31 @@ module Brick
|
|
56
56
|
@mutex.synchronize { @enable_routes = enable }
|
57
57
|
end
|
58
58
|
|
59
|
+
def enable_api
|
60
|
+
@mutex.synchronize { @enable_api }
|
61
|
+
end
|
62
|
+
|
63
|
+
def enable_api=(enable)
|
64
|
+
@mutex.synchronize { @enable_api = enable }
|
65
|
+
end
|
66
|
+
|
67
|
+
def api_root
|
68
|
+
ver = api_version
|
69
|
+
@mutex.synchronize { @api_root || "/api/#{ver}/" }
|
70
|
+
end
|
71
|
+
|
72
|
+
def api_root=(path)
|
73
|
+
@mutex.synchronize { @api_root = path }
|
74
|
+
end
|
75
|
+
|
76
|
+
def api_version
|
77
|
+
@mutex.synchronize { @api_version || 'v1' }
|
78
|
+
end
|
79
|
+
|
80
|
+
def api_version=(ver)
|
81
|
+
@mutex.synchronize { @api_version = ver }
|
82
|
+
end
|
83
|
+
|
59
84
|
# Additional table associations to use (Think of these as virtual foreign keys perhaps)
|
60
85
|
def additional_references
|
61
86
|
@mutex.synchronize { @additional_references }
|
data/lib/brick/extensions.rb
CHANGED
@@ -107,7 +107,8 @@ module ActiveRecord
|
|
107
107
|
bracket_name = nil
|
108
108
|
prefix = [prefix] unless prefix.is_a?(Array)
|
109
109
|
if (dsl = ::Brick.config.model_descrips[name] || brick_get_dsl)
|
110
|
-
dsl2 = +''
|
110
|
+
dsl2 = +'' # To replace our own DSL definition in case it needs to be expanded
|
111
|
+
dsl3 = +'' # To return expanded DSL that is nested from another model
|
111
112
|
klass = nil
|
112
113
|
dsl.each_char do |ch|
|
113
114
|
if bracket_name
|
@@ -129,11 +130,16 @@ module ActiveRecord
|
|
129
130
|
end
|
130
131
|
if klass.column_names.exclude?(parts.last) &&
|
131
132
|
(klass = (orig_class = klass).reflect_on_association(possible_dsl = parts.pop.to_sym)&.klass)
|
133
|
+
# Expand this entry which refers to an association name
|
132
134
|
members2, dsl2a = klass.brick_parse_dsl(build_array, prefix + [possible_dsl], translations, true)
|
133
135
|
members += members2
|
134
136
|
dsl2 << dsl2a
|
137
|
+
dsl3 << dsl2a
|
135
138
|
else
|
136
139
|
dsl2 << "[#{bracket_name}]"
|
140
|
+
if emit_dsl
|
141
|
+
dsl3 << "[#{prefix[1..-1].map { |p| "#{p.to_s}." }.join if prefix.length > 1}#{bracket_name}]"
|
142
|
+
end
|
137
143
|
members << parts
|
138
144
|
end
|
139
145
|
bracket_name = nil
|
@@ -145,15 +151,22 @@ module ActiveRecord
|
|
145
151
|
klass = self
|
146
152
|
else
|
147
153
|
dsl2 << ch
|
154
|
+
dsl3 << ch
|
148
155
|
end
|
149
156
|
end
|
150
157
|
# Rewrite the DSL in case it's now different from having to expand it
|
158
|
+
# if ::Brick.config.model_descrips[name] != dsl2
|
159
|
+
# puts ::Brick.config.model_descrips[name]
|
160
|
+
# puts dsl2.inspect
|
161
|
+
# puts dsl3.inspect
|
162
|
+
# binding.pry
|
163
|
+
# end
|
151
164
|
::Brick.config.model_descrips[name] = dsl2 unless emit_dsl
|
152
165
|
else # With no DSL available, still put this prefix into the JoinArray so we can get primary key (ID) info from this table
|
153
166
|
x = prefix.each_with_object(build_array) { |v, s| s[v.to_sym] }
|
154
167
|
x[prefix.last] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
|
155
168
|
end
|
156
|
-
emit_dsl ? [members,
|
169
|
+
emit_dsl ? [members, dsl3] : members
|
157
170
|
end
|
158
171
|
|
159
172
|
# If available, parse simple DSL attached to a model in order to provide a friendlier name.
|
@@ -221,13 +234,21 @@ module ActiveRecord
|
|
221
234
|
end
|
222
235
|
|
223
236
|
def self.bt_link(assoc_name)
|
224
|
-
model_underscore = name.underscore
|
225
237
|
assoc_name = CGI.escapeHTML(assoc_name.to_s)
|
226
|
-
model_path = Rails.application.routes.url_helpers.send("#{
|
238
|
+
model_path = Rails.application.routes.url_helpers.send("#{_brick_index}_path".to_sym)
|
227
239
|
av_class = Class.new.extend(ActionView::Helpers::UrlHelper)
|
228
240
|
av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('7')
|
229
241
|
link = av_class.link_to(name, model_path)
|
230
|
-
|
242
|
+
table_name == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
|
243
|
+
end
|
244
|
+
|
245
|
+
def self._brick_index
|
246
|
+
tbl_parts = table_name.split('.')
|
247
|
+
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.first == Apartment.default_schema
|
248
|
+
if (index = tbl_parts.map(&:underscore).join('_')) == index.singularize
|
249
|
+
index << '_index' # Rails applies an _index suffix to that route when the resource name is singular
|
250
|
+
end
|
251
|
+
index
|
231
252
|
end
|
232
253
|
|
233
254
|
def self.brick_import_template
|
@@ -407,7 +428,7 @@ module ActiveRecord
|
|
407
428
|
is_distinct = nil
|
408
429
|
wheres = {}
|
409
430
|
params.each do |k, v|
|
410
|
-
next if ['_brick_schema', '_brick_order'].include?(k)
|
431
|
+
next if ['_brick_schema', '_brick_order', 'controller', 'action'].include?(k)
|
411
432
|
|
412
433
|
case (ks = k.split('.')).length
|
413
434
|
when 1
|
@@ -427,6 +448,9 @@ module ActiveRecord
|
|
427
448
|
# %%% Skip the metadata columns
|
428
449
|
if selects&.empty? # Default to all columns
|
429
450
|
tbl_no_schema = table.name.split('.').last
|
451
|
+
# %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
|
452
|
+
# ActiveRecord::StatementInvalid (TinyTds::Error: DBPROCESS is dead or not enabled)
|
453
|
+
# Relevant info here: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/402
|
430
454
|
columns.each do |col|
|
431
455
|
col_alias = " AS #{col.name}_" if (col_name = col.name) == 'class'
|
432
456
|
selects << if is_mysql
|
@@ -726,7 +750,8 @@ Module.class_exec do
|
|
726
750
|
# Vabc instead of VABC)
|
727
751
|
full_class_name = +''
|
728
752
|
full_class_name << "::#{self.name}" unless self == Object
|
729
|
-
|
753
|
+
singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize
|
754
|
+
full_class_name << "::#{singular_class_name}"
|
730
755
|
if plural_class_name == 'BrickSwagger' ||
|
731
756
|
(
|
732
757
|
(::Brick.config.add_status || ::Brick.config.add_orphans) &&
|
@@ -1111,64 +1136,65 @@ class Object
|
|
1111
1136
|
end
|
1112
1137
|
return [new_controller_class, code + "end # BrickGem controller\n"]
|
1113
1138
|
when 'BrickSwagger'
|
1114
|
-
is_swagger = true
|
1139
|
+
is_swagger = true
|
1115
1140
|
end
|
1116
1141
|
|
1117
1142
|
self.protect_from_forgery unless: -> { self.request.format.js? }
|
1118
1143
|
self.define_method :index do
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1144
|
+
if (is_swagger || request.env['REQUEST_PATH'].start_with?(::Brick.api_root)) &&
|
1145
|
+
!params&.key?('_brick_schema') &&
|
1146
|
+
(referrer_params = request.env['HTTP_REFERER']&.split('?')&.last&.split('&')&.map { |x| x.split('=') }).present?
|
1147
|
+
if params
|
1148
|
+
referrer_params.each { |k, v| params.send(:parameters)[k] = v }
|
1149
|
+
else
|
1150
|
+
api_params = referrer_params&.to_h
|
1151
|
+
end
|
1152
|
+
end
|
1153
|
+
::Brick.set_db_schema(params || api_params)
|
1126
1154
|
|
1127
1155
|
if is_swagger
|
1128
|
-
json = { 'openapi': '3.0.1', 'info': { 'title': 'API
|
1156
|
+
json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
|
1129
1157
|
'servers': [
|
1130
|
-
{ 'url': '
|
1158
|
+
{ 'url': '{scheme}://{defaultHost}', 'variables': {
|
1159
|
+
'scheme': { 'default': request.env['rack.url_scheme'] },
|
1160
|
+
'defaultHost': { 'default': request.env['HTTP_HOST'] }
|
1161
|
+
} }
|
1131
1162
|
]
|
1132
1163
|
}
|
1133
|
-
json['paths'] = relations.inject({}) do |s,
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
'
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
'responses': { '200': { 'description': 'successful' } }
|
1164
|
+
json['paths'] = relations.inject({}) do |s, relation|
|
1165
|
+
unless ::Brick.config.enable_api == false
|
1166
|
+
table_description = relation.last[:description]
|
1167
|
+
s["#{::Brick.config.api_root}#{relation.first.tr('.', '/')}"] = {
|
1168
|
+
'get': {
|
1169
|
+
'summary': "list #{relation.first}",
|
1170
|
+
'description': table_description,
|
1171
|
+
'parameters': relation.last[:cols].map do |k, v|
|
1172
|
+
param = { 'name' => k, 'schema': { 'type': v.first } }
|
1173
|
+
if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
|
1174
|
+
param['description'] = col_descrip
|
1175
|
+
end
|
1176
|
+
param
|
1177
|
+
end,
|
1178
|
+
'responses': { '200': { 'description': 'successful' } }
|
1179
|
+
}
|
1150
1180
|
}
|
1151
|
-
|
1152
|
-
#
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
# }
|
1169
|
-
# ],
|
1170
|
-
}
|
1171
|
-
s
|
1181
|
+
|
1182
|
+
s["#{::Brick.config.api_root}#{relation.first.tr('.', '/')}/{id}"] = {
|
1183
|
+
'patch': {
|
1184
|
+
'summary': "update a #{relation.first.singularize}",
|
1185
|
+
'description': table_description,
|
1186
|
+
'parameters': relation.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
|
1187
|
+
param = { 'name' => k, 'schema': { 'type': v.first } }
|
1188
|
+
if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
|
1189
|
+
param['description'] = col_descrip
|
1190
|
+
end
|
1191
|
+
param
|
1192
|
+
end,
|
1193
|
+
'responses': { '200': { 'description': 'successful' } }
|
1194
|
+
}
|
1195
|
+
} unless relation.last.fetch(:isView, nil)
|
1196
|
+
s
|
1197
|
+
end
|
1172
1198
|
end
|
1173
1199
|
render inline: json.to_json, content_type: request.format
|
1174
1200
|
return
|
@@ -1176,6 +1202,14 @@ class Object
|
|
1176
1202
|
|
1177
1203
|
# Normal (non-swagger) request
|
1178
1204
|
|
1205
|
+
# We do all of this now so that bt_descrip and hm_counts are available on the model early in case the user
|
1206
|
+
# wants to do an ORDER BY based on any of that
|
1207
|
+
translations = {}
|
1208
|
+
join_array = ::Brick::JoinArray.new
|
1209
|
+
is_add_bts = is_add_hms = true
|
1210
|
+
# This builds out bt_descrip and hm_counts on the model
|
1211
|
+
model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
|
1212
|
+
|
1179
1213
|
# %%% Allow params to define which columns to use for order_by
|
1180
1214
|
# Overriding the default by providing a querystring param?
|
1181
1215
|
ordering = params['_brick_order']&.split(',')&.map(&:to_sym) || Object.send(:default_ordering, table_name, pk)
|
@@ -1189,8 +1223,9 @@ class Object
|
|
1189
1223
|
end
|
1190
1224
|
render inline: exported_csv, content_type: request.format
|
1191
1225
|
return
|
1192
|
-
elsif request.format == :js # Asking for JSON?
|
1193
|
-
|
1226
|
+
elsif request.format == :js || request.path.start_with?('/api/') # Asking for JSON?
|
1227
|
+
data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
|
1228
|
+
render inline: data.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
|
1194
1229
|
return
|
1195
1230
|
end
|
1196
1231
|
|
@@ -1206,7 +1241,7 @@ class Object
|
|
1206
1241
|
"b_r_#{v.first}.c_t_ AS \"b_r_#{v.first}_ct\""
|
1207
1242
|
end
|
1208
1243
|
end
|
1209
|
-
instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
1244
|
+
instance_variable_set("@#{table_name.pluralize}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
1210
1245
|
if namespace && (idx = lookup_context.prefixes.index(table_name))
|
1211
1246
|
lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
|
1212
1247
|
end
|
@@ -1221,32 +1256,34 @@ class Object
|
|
1221
1256
|
@_brick_erd = params['_brick_erd']&.to_i
|
1222
1257
|
end
|
1223
1258
|
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
is_pk_string = nil
|
1231
|
-
if (pk_col = model&.primary_key)
|
1232
|
-
code << " def show\n"
|
1233
|
-
code << " #{find_by_name = "find_#{singular_table_name}"}\n"
|
1259
|
+
unless is_swagger
|
1260
|
+
::Brick.set_db_schema
|
1261
|
+
_, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk)) if pk
|
1262
|
+
code << " def index\n"
|
1263
|
+
code << " @#{table_name.pluralize} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
1264
|
+
code << " @#{table_name.pluralize}.brick_select(params)\n"
|
1234
1265
|
code << " end\n"
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1266
|
+
|
1267
|
+
is_pk_string = nil
|
1268
|
+
if pk.present?
|
1269
|
+
code << " def show\n"
|
1270
|
+
code << " #{find_by_name = "find_#{singular_table_name}"}\n"
|
1271
|
+
code << " end\n"
|
1272
|
+
self.define_method :show do
|
1273
|
+
::Brick.set_db_schema(params)
|
1274
|
+
id = if model.columns_hash[pk.first]&.type == :string
|
1275
|
+
is_pk_string = true
|
1276
|
+
params[:id]
|
1277
|
+
else
|
1278
|
+
params[:id]&.split(/[\/,_]/)
|
1279
|
+
end
|
1280
|
+
id = id.first if id.is_a?(Array) && id.length == 1
|
1281
|
+
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1282
|
+
end
|
1245
1283
|
end
|
1246
|
-
end
|
1247
1284
|
|
1248
|
-
|
1249
|
-
|
1285
|
+
# By default, views get marked as read-only
|
1286
|
+
# unless model.readonly # (relation = relations[model.table_name]).key?(:isView)
|
1250
1287
|
code << " def new\n"
|
1251
1288
|
code << " @#{singular_table_name} = #{model.name}.new\n"
|
1252
1289
|
code << " end\n"
|
@@ -1280,7 +1317,7 @@ class Object
|
|
1280
1317
|
end
|
1281
1318
|
end
|
1282
1319
|
|
1283
|
-
if
|
1320
|
+
if pk.present?
|
1284
1321
|
# if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
|
1285
1322
|
# ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
1286
1323
|
# end
|
@@ -1327,9 +1364,9 @@ class Object
|
|
1327
1364
|
end
|
1328
1365
|
end
|
1329
1366
|
|
1330
|
-
code << "private\n" if
|
1367
|
+
code << "private\n" if pk.present? || is_need_params
|
1331
1368
|
|
1332
|
-
if
|
1369
|
+
if pk.present?
|
1333
1370
|
code << " def find_#{singular_table_name}
|
1334
1371
|
id = params[:id]&.split(/[\\/,_]/)
|
1335
1372
|
@#{singular_table_name} = #{model.name}.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
|
@@ -1357,16 +1394,16 @@ class Object
|
|
1357
1394
|
private params_name
|
1358
1395
|
# Get column names for params from relations[model.table_name][:cols].keys
|
1359
1396
|
end
|
1360
|
-
#
|
1397
|
+
end # unless is_swagger
|
1361
1398
|
code << "end # #{namespace_name}#{class_name}\n"
|
1362
1399
|
end # class definition
|
1363
1400
|
[built_controller, code]
|
1364
1401
|
end
|
1365
1402
|
|
1366
1403
|
def _brick_get_hm_assoc_name(relation, hm_assoc, source = nil)
|
1367
|
-
if (relation[:hm_counts][hm_assoc[:
|
1404
|
+
if (relation[:hm_counts][hm_assoc[:inverse_table]]&.> 1) &&
|
1368
1405
|
hm_assoc[:alternate_name] != (source || name.underscore)
|
1369
|
-
plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
|
1406
|
+
plural = "#{hm_assoc[:assoc_name]}_#{ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])}"
|
1370
1407
|
new_alt_name = (hm_assoc[:alternate_name] == name.underscore) ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural
|
1371
1408
|
# uniq = 1
|
1372
1409
|
# while same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }
|
@@ -1497,6 +1534,8 @@ module ActiveRecord::ConnectionHandling
|
|
1497
1534
|
r['schema']
|
1498
1535
|
end
|
1499
1536
|
relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
|
1537
|
+
# Both uppers and lowers as well as underscores?
|
1538
|
+
apply_double_underscore_patch if relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
1500
1539
|
relation = relations[relation_name]
|
1501
1540
|
relation[:isView] = true if r['table_type'] == 'VIEW'
|
1502
1541
|
relation[:description] = r['table_description'] if r['table_description']
|
@@ -1513,6 +1552,7 @@ module ActiveRecord::ConnectionHandling
|
|
1513
1552
|
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
1514
1553
|
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
|
1515
1554
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
1555
|
+
relation[:col_descrips][col_name] = r['column_description'] if r['column_description']
|
1516
1556
|
end
|
1517
1557
|
else # MySQL2 and OracleEnhanced act a little differently, bringing back an array for each row
|
1518
1558
|
schema_and_tables = case ActiveRecord::Base.connection.adapter_name
|
@@ -1542,6 +1582,9 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1542
1582
|
|
1543
1583
|
if (relation_name = r[1]) =~ /^[A-Z0-9_]+$/
|
1544
1584
|
relation_name.downcase!
|
1585
|
+
# Both uppers and lowers as well as underscores?
|
1586
|
+
elsif relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
1587
|
+
apply_double_underscore_patch
|
1545
1588
|
end
|
1546
1589
|
# Expect the default schema for SQL Server to be 'dbo'.
|
1547
1590
|
if (::Brick.is_oracle && r[0] != schema) || (is_mssql && r[0] != 'dbo')
|
@@ -1660,54 +1703,27 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1660
1703
|
end
|
1661
1704
|
end
|
1662
1705
|
|
1663
|
-
tables = []
|
1664
|
-
views = []
|
1665
|
-
table_class_length = 0
|
1666
|
-
view_class_length = 0
|
1667
1706
|
relations.each do |k, v|
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
class_name =
|
1675
|
-
if v.key?(:isView)
|
1676
|
-
view_class_length = class_name.length if class_name.length > view_class_length
|
1677
|
-
views
|
1678
|
-
else
|
1679
|
-
table_class_length = class_name.length if class_name.length > table_class_length
|
1680
|
-
tables
|
1681
|
-
end << [class_name, name_parts]
|
1682
|
-
end
|
1683
|
-
puts "\n" if tables.present? || views.present?
|
1684
|
-
if tables.present?
|
1685
|
-
puts "Classes that can be built from tables:"
|
1686
|
-
display_classes(tables, table_class_length)
|
1707
|
+
rel_name = k.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
|
1708
|
+
schema_names = rel_name[0..-2]
|
1709
|
+
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == Apartment.default_schema
|
1710
|
+
v[:schema] = schema_names.join('.') unless schema_names.empty?
|
1711
|
+
# %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
|
1712
|
+
v[:resource] = rel_name.last
|
1713
|
+
v[:class_name] = (schema_names + [rel_name.last.singularize]).map(&:camelize).join('::')
|
1687
1714
|
end
|
1688
|
-
if views.present?
|
1689
|
-
puts "Classes that can be built from views:"
|
1690
|
-
display_classes(views, view_class_length)
|
1691
|
-
end
|
1692
|
-
|
1693
1715
|
::Brick.load_additional_references if initializer_loaded
|
1694
1716
|
end
|
1695
1717
|
|
1696
|
-
def display_classes(rels, max_length)
|
1697
|
-
rels.sort.each do |rel|
|
1698
|
-
rel_link = rel.last.dup.map(&:underscore)
|
1699
|
-
rel_link[-1] = rel_link[-1].pluralize
|
1700
|
-
puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{rel_link.join('/')}"
|
1701
|
-
end
|
1702
|
-
puts "\n"
|
1703
|
-
end
|
1704
|
-
|
1705
1718
|
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
|
1706
1719
|
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
|
1707
1720
|
sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
|
1708
1721
|
pg_catalog.obj_description(
|
1709
|
-
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass, 'pg_class'
|
1710
|
-
) AS table_description,
|
1722
|
+
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, 'pg_class'
|
1723
|
+
) AS table_description,
|
1724
|
+
pg_catalog.col_description(
|
1725
|
+
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, c.ordinal_position
|
1726
|
+
) AS column_description," if is_postgres}
|
1711
1727
|
c.column_name, c.data_type,
|
1712
1728
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
1713
1729
|
kcu.constraint_type AS const, kcu.constraint_name AS \"key\",
|
@@ -1751,6 +1767,43 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1751
1767
|
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
|
1752
1768
|
[ar_smtn, ar_imtn]
|
1753
1769
|
end
|
1770
|
+
|
1771
|
+
def apply_double_underscore_patch
|
1772
|
+
unless @double_underscore_applied
|
1773
|
+
# Same as normal #camelize and #underscore, just that double-underscores turn into a single underscore
|
1774
|
+
ActiveSupport::Inflector.class_eval do
|
1775
|
+
def camelize(term, uppercase_first_letter = true)
|
1776
|
+
strings = term.to_s.split('__').map do |string|
|
1777
|
+
# String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent.
|
1778
|
+
if !uppercase_first_letter || uppercase_first_letter == :lower
|
1779
|
+
string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match }
|
1780
|
+
else
|
1781
|
+
string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match }
|
1782
|
+
end
|
1783
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
|
1784
|
+
word = $2
|
1785
|
+
substituted = inflections.acronyms[word] || word.capitalize! || word
|
1786
|
+
$1 ? "::#{substituted}" : substituted
|
1787
|
+
end
|
1788
|
+
string
|
1789
|
+
end
|
1790
|
+
strings.join('_')
|
1791
|
+
end
|
1792
|
+
|
1793
|
+
def underscore(camel_cased_word)
|
1794
|
+
return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)
|
1795
|
+
camel_cased_word.to_s.gsub("::", "/").split('_').map do |word|
|
1796
|
+
word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
|
1797
|
+
word.gsub!(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }
|
1798
|
+
word.tr!("-", "_")
|
1799
|
+
word.downcase!
|
1800
|
+
word
|
1801
|
+
end.join('__')
|
1802
|
+
end
|
1803
|
+
end
|
1804
|
+
@double_underscore_applied = true
|
1805
|
+
end
|
1806
|
+
end
|
1754
1807
|
end
|
1755
1808
|
|
1756
1809
|
# ==========================================
|
@@ -1777,7 +1830,7 @@ module Brick
|
|
1777
1830
|
|
1778
1831
|
class << self
|
1779
1832
|
def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
|
1780
|
-
bt_assoc_name = ::Brick.namify(fk[2],
|
1833
|
+
bt_assoc_name = ::Brick.namify(fk[2], :downcase)
|
1781
1834
|
unless is_polymorphic
|
1782
1835
|
bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
|
1783
1836
|
bt_assoc_name[-3] == '_' ? bt_assoc_name[0..-4] : bt_assoc_name[0..-3]
|
@@ -1878,6 +1931,11 @@ module Brick
|
|
1878
1931
|
|
1879
1932
|
return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[1] == exclusion[0] && fk[2] == exclusion[1] && primary_table == exclusion[2] } || hms.nil?
|
1880
1933
|
|
1934
|
+
# if fk[1].end_with?('Suppliers') && fk[4] == 'People'
|
1935
|
+
# puts fk.inspect
|
1936
|
+
# binding.pry
|
1937
|
+
# end
|
1938
|
+
|
1881
1939
|
if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
|
1882
1940
|
if assoc_hm[:fk].is_a?(String)
|
1883
1941
|
assoc_hm[:fk] = [assoc_hm[:fk], fk[2]] unless fk[2] == assoc_hm[:fk]
|
@@ -1885,7 +1943,6 @@ module Brick
|
|
1885
1943
|
assoc_hm[:fk] << fk[2]
|
1886
1944
|
end
|
1887
1945
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
1888
|
-
assoc_hm[:inverse] = assoc_bt
|
1889
1946
|
else
|
1890
1947
|
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == Apartment.default_schema
|
1891
1948
|
for_tbl
|
@@ -1896,7 +1953,7 @@ module Brick
|
|
1896
1953
|
inverse_table: inv_tbl, inverse: assoc_bt }
|
1897
1954
|
assoc_hm[:polymorphic] = true if is_polymorphic
|
1898
1955
|
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
1899
|
-
hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
|
1956
|
+
this_hm_count = hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
|
1900
1957
|
end
|
1901
1958
|
assoc_bt[:inverse] = assoc_hm
|
1902
1959
|
end
|
@@ -159,7 +159,7 @@ module Brick
|
|
159
159
|
hms_columns << hm_entry
|
160
160
|
when 'show', 'new', 'update'
|
161
161
|
hm_stuff << if hm_fk_name
|
162
|
-
"<%= link_to '#{assoc_name}', #{hm_assoc.klass.
|
162
|
+
"<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}", pk)} }) %>\n"
|
163
163
|
else # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
|
164
164
|
assoc_name
|
165
165
|
end
|
@@ -173,12 +173,13 @@ module Brick
|
|
173
173
|
# environment or whatever, then get either the controllers or routes list instead
|
174
174
|
apartment_default_schema = ::Brick.apartment_multitenant && Apartment.default_schema
|
175
175
|
table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).each_with_object({}) do |tbl, s|
|
176
|
+
binding.pry if tbl.is_a?(Symbol)
|
176
177
|
if (tbl_parts = tbl.split('.')).first == apartment_default_schema
|
177
178
|
tbl = tbl_parts.last
|
178
179
|
end
|
179
180
|
s[tbl] = nil
|
180
181
|
end.keys.sort.each_with_object(+'') do |v, s|
|
181
|
-
s << "<option value=\"#{v.underscore.gsub('.', '/')
|
182
|
+
s << "<option value=\"#{v.underscore.gsub('.', '/')}\">#{v}</option>"
|
182
183
|
end.html_safe
|
183
184
|
table_options << '<option value="brick_status">(Status)</option>'.html_safe if ::Brick.config.add_status
|
184
185
|
table_options << '<option value="brick_orphans">(Orphans)</option>'.html_safe if is_orphans
|
@@ -351,15 +352,39 @@ def hide_bcrypt(val, max_len = 200)
|
|
351
352
|
end
|
352
353
|
def display_value(col_type, val)
|
353
354
|
case col_type
|
354
|
-
when 'geometry'
|
355
|
+
when 'geometry', 'geography'
|
355
356
|
if Object.const_defined?('RGeo')
|
356
357
|
@is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2' if @is_mysql.nil?
|
357
|
-
if @
|
358
|
-
|
359
|
-
|
360
|
-
|
358
|
+
@is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if @is_mssql.nil?
|
359
|
+
val_err = nil
|
360
|
+
if @is_mysql || @is_mssql
|
361
|
+
# MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
|
362
|
+
if (srid = val&.[](0..3)&.unpack('I'))
|
363
|
+
val = val.force_encoding('BINARY')[4..-1].bytes
|
364
|
+
|
365
|
+
# MSSQL spatial bitwise flags, often 0C for a point:
|
366
|
+
# xxxx xxx1 = HasZValues
|
367
|
+
# xxxx xx1x = HasMValues
|
368
|
+
# xxxx x1xx = IsValid
|
369
|
+
# xxxx 1xxx = IsSinglePoint
|
370
|
+
# xxx1 xxxx = IsSingleLineSegment
|
371
|
+
# xx1x xxxx = IsWholeGlobe
|
372
|
+
# Convert Microsoft's unique geography binary to standard WKB
|
373
|
+
# (MSSQL point usually has two doubles, lng / lat, and can also have Z)
|
374
|
+
if @is_mssql
|
375
|
+
if val[0] == 1 && (val[1] & 8 > 0) && # Single point?
|
376
|
+
(val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
|
377
|
+
idx = 2
|
378
|
+
new_val = [0, 0, 0, 0, 1]
|
379
|
+
new_val.concat(val[idx - 8...idx].reverse) while (idx += 8) <= val.length
|
380
|
+
val = new_val
|
381
|
+
else
|
382
|
+
val_err = '(Microsoft internal SQL geography type)'
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
361
386
|
end
|
362
|
-
RGeo::WKRep::WKBParser.new.parse(val)
|
387
|
+
val_err || (val ? RGeo::WKRep::WKBParser.new.parse(val.pack('c*')) : nil)
|
363
388
|
else
|
364
389
|
'(Add RGeo gem to parse geometry detail)'
|
365
390
|
end
|
@@ -448,7 +473,7 @@ function changeout(href, param, value, trimAfter) {
|
|
448
473
|
var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
|
449
474
|
if (param === undefined || param === null || param === -1) {
|
450
475
|
hrefParts = hrefParts[0].split(\"://\");
|
451
|
-
var pathParts = hrefParts[hrefParts.length - 1].split(\"/\");
|
476
|
+
var pathParts = hrefParts[hrefParts.length - 1].split(\"/\").filter(function (pp) {return pp !== \"\";});
|
452
477
|
if (value === undefined)
|
453
478
|
// A couple possibilities if it's namespaced, starting with two parts in the path -- and then try just one
|
454
479
|
return [pathParts.slice(1, 3).join('/'), pathParts.slice(1, 2)[0]];
|
@@ -562,7 +587,7 @@ erDiagram
|
|
562
587
|
<%= \"#\{model_short_name} }o..o{ #\{hm_name} : \\\"#\{hm.first}\\\"\".html_safe %><%
|
563
588
|
else # has_many
|
564
589
|
%> <%= \"#\{model_short_name} ||--o{ #\{hm_name} : \\\"#\{
|
565
|
-
|
590
|
+
hm.first.to_s unless hm.first.to_s.downcase == hm_class.name.underscore.pluralize.tr('/', '_')
|
566
591
|
}\\\"\".html_safe %><%
|
567
592
|
end %>
|
568
593
|
<% end
|
@@ -604,7 +629,7 @@ erDiagram
|
|
604
629
|
end
|
605
630
|
if Object.const_defined?('DutyFree')
|
606
631
|
template_link = "
|
607
|
-
<%= link_to 'CSV', #{
|
632
|
+
<%= link_to 'CSV', #{@_brick_model._brick_index}_path(format: :csv) %> <a href=\"#\" id=\"sheetsLink\">Sheets</a>
|
608
633
|
<div id=\"dropper\" contenteditable=\"true\"></div>
|
609
634
|
<input type=\"button\" id=\"btnImport\" value=\"Import\">
|
610
635
|
|
@@ -667,7 +692,7 @@ erDiagram
|
|
667
692
|
console.log(\"x1\", sheetUrl);
|
668
693
|
|
669
694
|
// Get JSON data
|
670
|
-
fetch(changeout(<%= #{
|
695
|
+
fetch(changeout(<%= #{@_brick_model._brick_index}_path(format: :js).inspect.html_safe %>, \"_brick_schema\", brickSchema)).then(function (response) {
|
671
696
|
response.json().then(function (data) {
|
672
697
|
gapi.client.sheets.spreadsheets.values.append({
|
673
698
|
spreadsheetId: spreadsheetId,
|
@@ -698,10 +723,10 @@ erDiagram
|
|
698
723
|
<select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
|
699
724
|
<select id=\"tbl\">#{table_options}</select>
|
700
725
|
<table id=\"resourceName\"><tr>
|
701
|
-
<td><h1>#{
|
726
|
+
<td><h1>#{model_name}</h1></td>
|
702
727
|
<td id=\"imgErd\" title=\"Show ERD\"></td>
|
703
728
|
</tr></table>#{template_link}<%
|
704
|
-
if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
|
729
|
+
if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)).present? %><%=
|
705
730
|
description %><br><%
|
706
731
|
end
|
707
732
|
# FILTER PARAMETERS
|
@@ -712,10 +737,10 @@ erDiagram
|
|
712
737
|
origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
|
713
738
|
if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| fk[:fk] == key_parts.last }) &&
|
714
739
|
(obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
|
715
|
-
<h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.
|
740
|
+
<h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index\}_path\".to_sym, id) %></h3><%
|
716
741
|
end
|
717
742
|
end %>
|
718
|
-
(<%= link_to 'See all #{
|
743
|
+
(<%= link_to 'See all #{model_name.split('::').last.pluralize}', #{@_brick_model._brick_index}_path %>)
|
719
744
|
<% end
|
720
745
|
# COLUMN EXCLUSIONS
|
721
746
|
if @_brick_excl&.present? %>
|
@@ -768,7 +793,7 @@ erDiagram
|
|
768
793
|
end
|
769
794
|
elsif col # HM column
|
770
795
|
s << \"<th#\{' x-order=\"' + col_name + '\"' if true}>#\{col[2]} \"
|
771
|
-
s << (col.first ? \"#\{col[3]}\" : \"#\{link_to(col[3], send(\"#\{col[1].
|
796
|
+
s << (col.first ? \"#\{col[3]}\" : \"#\{link_to(col[3], send(\"#\{col[1]._brick_index}_path\"))}\")
|
772
797
|
else # Bad column name!
|
773
798
|
s << \"<th title=\\\"<< Unknown column >>\\\">#\{col_name}\"
|
774
799
|
end
|
@@ -776,7 +801,11 @@ erDiagram
|
|
776
801
|
end.html_safe
|
777
802
|
%></tr></thead>
|
778
803
|
<tbody>
|
779
|
-
<%
|
804
|
+
<% # %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
|
805
|
+
# ActiveRecord::StatementTimeout in Warehouse::ColdRoomTemperatures_Archive#index
|
806
|
+
# TinyTds::Error: Adaptive Server connection timed out
|
807
|
+
# (After restarting the server it worked fine again.)
|
808
|
+
@#{table_name}.each do |#{obj_name}|
|
780
809
|
hms_cols = {#{hms_columns.join(', ')}} %>
|
781
810
|
<tr>#{"
|
782
811
|
<td><%= link_to '⇛', #{path_obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
|
@@ -811,10 +840,11 @@ erDiagram
|
|
811
840
|
else
|
812
841
|
\"#\{hms_col[1] || 'View'\} #\{hms_col.first}\"
|
813
842
|
end %>
|
814
|
-
<%= link_to txt, send(\"#\{klass.
|
843
|
+
<%= link_to txt, send(\"#\{klass._brick_index}_path\".to_sym, hms_col[2]) unless hms_col[1]&.zero? %>
|
815
844
|
<% end
|
816
845
|
elsif (col = cols[col_name])
|
817
|
-
|
846
|
+
col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
|
847
|
+
%><%= display_value(col_type || col&.sql_type, val) %><%
|
818
848
|
else # Bad column name!
|
819
849
|
%>?<%
|
820
850
|
end
|
@@ -908,7 +938,7 @@ erDiagram
|
|
908
938
|
if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
|
909
939
|
description %><br><%
|
910
940
|
end
|
911
|
-
%><%= link_to '(See all #{obj_name.pluralize})', #{
|
941
|
+
%><%= link_to '(See all #{obj_name.pluralize})', #{@_brick_model._brick_index}_path %>
|
912
942
|
#{erd_markup}
|
913
943
|
<% if obj %>
|
914
944
|
<br><br>
|
@@ -974,7 +1004,8 @@ end
|
|
974
1004
|
\"<span class=\\\"orphan\\\">Orphaned ID: #\{val}</span>\".html_safe
|
975
1005
|
end %>
|
976
1006
|
<% else
|
977
|
-
|
1007
|
+
col_type = col.sql_type == 'geography' ? col.sql_type : col.type
|
1008
|
+
case (col_type ||= col.sql_type)
|
978
1009
|
when :string, :text %>
|
979
1010
|
<% if is_bcrypt?(val) # || .readonly?
|
980
1011
|
is_revert = false %>
|
@@ -1008,8 +1039,8 @@ end
|
|
1008
1039
|
<% when :binary, :primary_key
|
1009
1040
|
is_revert = false %>
|
1010
1041
|
<% else %>
|
1011
|
-
<%=
|
1012
|
-
|
1042
|
+
<%= is_revert = false
|
1043
|
+
display_value(col_type, val) %>
|
1013
1044
|
<% end
|
1014
1045
|
end
|
1015
1046
|
if is_revert
|
@@ -1077,7 +1108,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
|
|
1077
1108
|
var imgErd = document.getElementById(\"imgErd\");
|
1078
1109
|
var mermaidErd = document.getElementById(\"mermaidErd\");
|
1079
1110
|
var mermaidCode;
|
1080
|
-
var cbs = {<%= callbacks.map { |k, v| \"#\{k}: \\\"#\{v.
|
1111
|
+
var cbs = {<%= callbacks.map { |k, v| \"#\{k}: \\\"#\{send(\"#\{v._brick_index}_path\".to_sym)}\\\"\" }.join(', ').html_safe %>};
|
1081
1112
|
if (imgErd) imgErd.addEventListener(\"click\", showErd);
|
1082
1113
|
function showErd() {
|
1083
1114
|
imgErd.style.display = \"none\";
|
@@ -1101,7 +1132,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
|
|
1101
1132
|
function (evt) {
|
1102
1133
|
location.href = changeout(changeout(
|
1103
1134
|
changeout(location.href, '_brick_order', null), // Remove any ordering
|
1104
|
-
-1, cbs[this.id]), \"_brick_erd\", \"1\");
|
1135
|
+
-1, cbs[this.id].replace(/^[\/]+/, \"\")), \"_brick_erd\", \"1\");
|
1105
1136
|
}
|
1106
1137
|
);
|
1107
1138
|
}
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -125,8 +125,8 @@ module Brick
|
|
125
125
|
class << self
|
126
126
|
attr_accessor :default_schema, :db_schemas, :routes_done, :is_oracle
|
127
127
|
|
128
|
-
def set_db_schema(params)
|
129
|
-
schema = params['_brick_schema'] || 'public'
|
128
|
+
def set_db_schema(params = nil)
|
129
|
+
schema = (params ? params['_brick_schema'] : ::Brick.default_schema) || 'public'
|
130
130
|
if schema && ::Brick.db_schemas&.include?(schema)
|
131
131
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
132
132
|
schema
|
@@ -152,17 +152,19 @@ module Brick
|
|
152
152
|
end
|
153
153
|
|
154
154
|
# Convert spaces to underscores if the second character and onwards is mixed case
|
155
|
-
def namify(name,
|
156
|
-
|
155
|
+
def namify(name, action = nil)
|
156
|
+
has_uppers = name =~ /[A-Z]+/
|
157
|
+
has_lowers = name =~ /[a-z]+/
|
158
|
+
name.downcase! if has_uppers && action == :downcase
|
157
159
|
if name.include?(' ')
|
158
160
|
# All uppers or all lowers?
|
159
|
-
if
|
161
|
+
if !has_uppers || !has_lowers
|
160
162
|
name.titleize.tr(' ', '_')
|
161
163
|
else # Mixed uppers and lowers -- just remove existing spaces
|
162
164
|
name.tr(' ', '')
|
163
165
|
end
|
164
166
|
else
|
165
|
-
name
|
167
|
+
action == :underscore ? name.underscore : name
|
166
168
|
end
|
167
169
|
end
|
168
170
|
|
@@ -266,6 +268,26 @@ module Brick
|
|
266
268
|
!!Brick.config.enable_routes
|
267
269
|
end
|
268
270
|
|
271
|
+
# @api public
|
272
|
+
def enable_api=(path)
|
273
|
+
Brick.config.enable_api = path
|
274
|
+
end
|
275
|
+
|
276
|
+
# @api public
|
277
|
+
def enable_api
|
278
|
+
Brick.config.enable_api
|
279
|
+
end
|
280
|
+
|
281
|
+
# @api public
|
282
|
+
def api_root=(path)
|
283
|
+
Brick.config.api_root = path
|
284
|
+
end
|
285
|
+
|
286
|
+
# @api public
|
287
|
+
def api_root
|
288
|
+
Brick.config.api_root
|
289
|
+
end
|
290
|
+
|
269
291
|
# @api public
|
270
292
|
def skip_database_views=(value)
|
271
293
|
Brick.config.skip_database_views = value
|
@@ -472,6 +494,13 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
472
494
|
def version
|
473
495
|
VERSION::STRING
|
474
496
|
end
|
497
|
+
|
498
|
+
def display_classes(rels, max_length)
|
499
|
+
rels.sort.each do |rel|
|
500
|
+
puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{rel.last}"
|
501
|
+
end
|
502
|
+
puts "\n"
|
503
|
+
end
|
475
504
|
end
|
476
505
|
|
477
506
|
module RouteSet
|
@@ -479,35 +508,65 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
479
508
|
return super if ::Brick.routes_done
|
480
509
|
|
481
510
|
::Brick.routes_done = true
|
511
|
+
tables = []
|
512
|
+
views = []
|
513
|
+
table_class_length = 38 # Length of "Classes that can be built from tables:"
|
514
|
+
view_class_length = 37 # Length of "Classes that can be built from views:"
|
482
515
|
existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
|
483
516
|
::Rails.application.routes.append do
|
484
517
|
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
485
518
|
# If auto-controllers and auto-models are both enabled then this makes sense:
|
486
|
-
::Brick.relations.each do |
|
487
|
-
|
488
|
-
schema_names = rel_name[0..-2]
|
489
|
-
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == Apartment.default_schema
|
490
|
-
# %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
|
491
|
-
k = rel_name.last
|
492
|
-
unless existing_controllers.key?(controller_name = k.pluralize)
|
519
|
+
::Brick.relations.each do |k, v|
|
520
|
+
unless !(controller_name = v.fetch(:resource, nil)&.pluralize) || existing_controllers.key?(controller_name)
|
493
521
|
options = {}
|
494
522
|
options[:only] = [:index, :show] if v.key?(:isView)
|
495
|
-
|
496
|
-
|
497
|
-
|
523
|
+
full_resource = nil
|
524
|
+
if (schema_name = v.fetch(:schema, nil)) # && !Object.const_defined('Apartment')
|
525
|
+
send(:namespace, schema_name) do
|
526
|
+
send(:resources, v[:resource].to_sym, **options)
|
498
527
|
end
|
528
|
+
full_resource = "#{schema_name}/#{v[:resource]}"
|
529
|
+
send(:get, "#{::Brick.api_root}#{full_resource}", { to: "#{schema_name}/#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
|
499
530
|
else
|
500
|
-
send(:resources,
|
531
|
+
send(:resources, v[:resource].to_sym, **options)
|
532
|
+
# Normally goes to something like: /api/v1/employees
|
533
|
+
send(:get, "#{::Brick.api_root}#{v[:resource]}", { to: "#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
|
534
|
+
end
|
535
|
+
|
536
|
+
if (class_name = v.fetch(:class_name, nil))
|
537
|
+
if v.key?(:isView)
|
538
|
+
view_class_length = class_name.length if class_name.length > view_class_length
|
539
|
+
views
|
540
|
+
else
|
541
|
+
table_class_length = class_name.length if class_name.length > table_class_length
|
542
|
+
tables
|
543
|
+
end << [class_name, full_resource || v[:resource]]
|
501
544
|
end
|
502
545
|
end
|
503
|
-
if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
|
504
|
-
get('/brick_status', to: 'brick_gem#status', as: 'brick_status')
|
505
|
-
end
|
506
|
-
if ::Brick.config.add_orphans && instance_variable_get(:@set).named_routes.names.exclude?(:brick_orphans)
|
507
|
-
get('/brick_orphans', to: 'brick_gem#orphans', as: 'brick_orphans')
|
508
|
-
end
|
509
546
|
end
|
510
|
-
|
547
|
+
puts "\n" if tables.present? || views.present?
|
548
|
+
if tables.present?
|
549
|
+
puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
|
550
|
+
puts "======================================#{' ' * (table_class_length - 38)} ====="
|
551
|
+
::Brick.display_classes(tables, table_class_length)
|
552
|
+
end
|
553
|
+
if views.present?
|
554
|
+
puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
|
555
|
+
puts "=====================================#{' ' * (view_class_length - 37)} ====="
|
556
|
+
::Brick.display_classes(views, view_class_length)
|
557
|
+
end
|
558
|
+
|
559
|
+
if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
|
560
|
+
get('/brick_status', to: 'brick_gem#status', as: 'brick_status')
|
561
|
+
end
|
562
|
+
if ::Brick.config.add_orphans && instance_variable_get(:@set).named_routes.names.exclude?(:brick_orphans)
|
563
|
+
get('/brick_orphans', to: 'brick_gem#orphans', as: 'brick_orphans')
|
564
|
+
end
|
565
|
+
if Object.const_defined?('Rswag::Ui') && doc_endpoint = Rswag::Ui.config.config_object[:urls].last
|
566
|
+
# Serves JSON swagger info from a path such as '/api-docs/v1/swagger.json'
|
567
|
+
puts "Mounting swagger info endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}"
|
568
|
+
send(:get, doc_endpoint[:url], { to: 'brick_swagger#index' })
|
569
|
+
end
|
511
570
|
end
|
512
571
|
super
|
513
572
|
end
|
@@ -147,6 +147,8 @@ module Brick
|
|
147
147
|
# Brick.enable_controllers = true # Setting this to \"false\" will disable controllers in development
|
148
148
|
# Brick.enable_views = true # Setting this to \"false\" will disable views in development
|
149
149
|
|
150
|
+
# ::Brick.api_root = '/api/v1/' # Path from which to serve out API resources when the RSwag gem is present
|
151
|
+
|
150
152
|
# # By default models are auto-created for database views, and set to be read-only. This can be skipped.
|
151
153
|
# Brick.skip_database_views = true
|
152
154
|
|
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.74
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-09-
|
11
|
+
date: 2022-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|