brick 1.0.71 → 1.0.73
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 +256 -144
- data/lib/brick/frameworks/rails/engine.rb +60 -29
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +95 -9
- data/lib/generators/brick/install_generator.rb +2 -0
- data/lib/generators/brick/migrations_generator.rb +42 -11
- metadata +2 -2
data/lib/brick/extensions.rb
CHANGED
@@ -68,13 +68,14 @@ module ActiveRecord
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def self._brick_primary_key(relation = nil)
|
71
|
-
return
|
71
|
+
return @_brick_primary_key if instance_variable_defined?(:@_brick_primary_key)
|
72
72
|
|
73
73
|
pk = begin
|
74
74
|
primary_key.is_a?(String) ? [primary_key] : primary_key || []
|
75
75
|
rescue
|
76
76
|
[]
|
77
77
|
end
|
78
|
+
pk.map! { |pk_part| pk_part =~ /^[A-Z0-9_]+$/ ? pk_part.downcase : pk_part } unless connection.adapter_name == 'MySQL2'
|
78
79
|
# Just return [] if we're missing any part of the primary key. (PK is usually just "id")
|
79
80
|
if relation && pk.present?
|
80
81
|
@_brick_primary_key ||= pk.any? { |pk_part| !relation[:cols].key?(pk_part) } ? [] : pk
|
@@ -106,7 +107,8 @@ module ActiveRecord
|
|
106
107
|
bracket_name = nil
|
107
108
|
prefix = [prefix] unless prefix.is_a?(Array)
|
108
109
|
if (dsl = ::Brick.config.model_descrips[name] || brick_get_dsl)
|
109
|
-
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
|
110
112
|
klass = nil
|
111
113
|
dsl.each_char do |ch|
|
112
114
|
if bracket_name
|
@@ -128,11 +130,16 @@ module ActiveRecord
|
|
128
130
|
end
|
129
131
|
if klass.column_names.exclude?(parts.last) &&
|
130
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
|
131
134
|
members2, dsl2a = klass.brick_parse_dsl(build_array, prefix + [possible_dsl], translations, true)
|
132
135
|
members += members2
|
133
136
|
dsl2 << dsl2a
|
137
|
+
dsl3 << dsl2a
|
134
138
|
else
|
135
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
|
136
143
|
members << parts
|
137
144
|
end
|
138
145
|
bracket_name = nil
|
@@ -144,18 +151,22 @@ module ActiveRecord
|
|
144
151
|
klass = self
|
145
152
|
else
|
146
153
|
dsl2 << ch
|
154
|
+
dsl3 << ch
|
147
155
|
end
|
148
156
|
end
|
149
|
-
# Rewrite the DSL
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
164
|
+
::Brick.config.model_descrips[name] = dsl2 unless emit_dsl
|
154
165
|
else # With no DSL available, still put this prefix into the JoinArray so we can get primary key (ID) info from this table
|
155
166
|
x = prefix.each_with_object(build_array) { |v, s| s[v.to_sym] }
|
156
167
|
x[prefix.last] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
|
157
168
|
end
|
158
|
-
emit_dsl ? [members,
|
169
|
+
emit_dsl ? [members, dsl3] : members
|
159
170
|
end
|
160
171
|
|
161
172
|
# If available, parse simple DSL attached to a model in order to provide a friendlier name.
|
@@ -223,13 +234,21 @@ module ActiveRecord
|
|
223
234
|
end
|
224
235
|
|
225
236
|
def self.bt_link(assoc_name)
|
226
|
-
model_underscore = name.underscore
|
227
237
|
assoc_name = CGI.escapeHTML(assoc_name.to_s)
|
228
|
-
model_path = Rails.application.routes.url_helpers.send("#{
|
238
|
+
model_path = Rails.application.routes.url_helpers.send("#{_brick_index}_path".to_sym)
|
229
239
|
av_class = Class.new.extend(ActionView::Helpers::UrlHelper)
|
230
240
|
av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('7')
|
231
241
|
link = av_class.link_to(name, model_path)
|
232
|
-
|
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
|
233
252
|
end
|
234
253
|
|
235
254
|
def self.brick_import_template
|
@@ -323,7 +342,7 @@ module ActiveRecord
|
|
323
342
|
end
|
324
343
|
|
325
344
|
class Relation
|
326
|
-
attr_reader :_brick_chains
|
345
|
+
attr_reader :_brick_chains, :_arel_applied_aliases
|
327
346
|
|
328
347
|
# CLASS STUFF
|
329
348
|
def _recurse_arel(piece, prefix = '')
|
@@ -361,7 +380,7 @@ module ActiveRecord
|
|
361
380
|
# parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
|
362
381
|
# binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
|
363
382
|
if table.is_a?(Arel::Nodes::TableAlias)
|
364
|
-
alias_name = table.right
|
383
|
+
@_arel_applied_aliases << (alias_name = table.right)
|
365
384
|
table = table.left
|
366
385
|
end
|
367
386
|
(_brick_chains[table._arel_table_type] ||= []) << (alias_name || table.table_alias || table.name)
|
@@ -387,6 +406,7 @@ module ActiveRecord
|
|
387
406
|
|
388
407
|
# INSTANCE STUFF
|
389
408
|
def _arel_alias_names
|
409
|
+
@_arel_applied_aliases = []
|
390
410
|
# %%% If with Rails 3.1 and older you get "NoMethodError: undefined method `eq' for nil:NilClass"
|
391
411
|
# when trying to call relation.arel, then somewhere along the line while navigating a has_many
|
392
412
|
# relationship it can't find the proper foreign key.
|
@@ -408,7 +428,7 @@ module ActiveRecord
|
|
408
428
|
is_distinct = nil
|
409
429
|
wheres = {}
|
410
430
|
params.each do |k, v|
|
411
|
-
next if ['_brick_schema', '_brick_order'].include?(k)
|
431
|
+
next if ['_brick_schema', '_brick_order', 'controller', 'action'].include?(k)
|
412
432
|
|
413
433
|
case (ks = k.split('.')).length
|
414
434
|
when 1
|
@@ -428,23 +448,11 @@ module ActiveRecord
|
|
428
448
|
# %%% Skip the metadata columns
|
429
449
|
if selects&.empty? # Default to all columns
|
430
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
|
431
454
|
columns.each do |col|
|
432
|
-
if (col_name = col.name) == 'class'
|
433
|
-
col_alias = " AS #{col.name}_"
|
434
|
-
else
|
435
|
-
alias_name = nil
|
436
|
-
idx = 0
|
437
|
-
col_name.each_char do |c|
|
438
|
-
unless (c >= 'a' && c <= 'z') ||
|
439
|
-
c == '_' ||
|
440
|
-
(c >= 'A' && c <= 'Z') ||
|
441
|
-
(c >= '0' && c <= '9')
|
442
|
-
(alias_name ||= col_name.dup)[idx] = 'x'
|
443
|
-
end
|
444
|
-
++idx
|
445
|
-
end
|
446
|
-
col_alias = " AS #{alias_name}" if alias_name
|
447
|
-
end
|
455
|
+
col_alias = " AS #{col.name}_" if (col_name = col.name) == 'class'
|
448
456
|
selects << if is_mysql
|
449
457
|
"`#{tbl_no_schema}`.`#{col_name}`#{col_alias}"
|
450
458
|
elsif is_postgres || is_mssql
|
@@ -452,8 +460,8 @@ module ActiveRecord
|
|
452
460
|
cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col_name)&.first&.start_with?('xml')
|
453
461
|
"\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
|
454
462
|
elsif col.type # Could be Sqlite or Oracle
|
455
|
-
if col_alias
|
456
|
-
"#{tbl_no_schema}
|
463
|
+
if col_alias || !(/^[a-z0-9_]+$/ =~ col_name)
|
464
|
+
"#{tbl_no_schema}.\"#{col_name}\"#{col_alias}"
|
457
465
|
else
|
458
466
|
"#{tbl_no_schema}.#{col_name}"
|
459
467
|
end
|
@@ -478,9 +486,14 @@ module ActiveRecord
|
|
478
486
|
next if chains[k1].nil?
|
479
487
|
|
480
488
|
tbl_name = (field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])).split('.').last
|
489
|
+
# If it's Oracle, quote any AREL aliases that had been applied
|
490
|
+
tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(tbl_name)
|
481
491
|
field_tbl_name = nil
|
482
492
|
v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
|
493
|
+
# binding.pry if chains[sel_col.first].nil?
|
483
494
|
field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
|
495
|
+
# If it's Oracle, quote any AREL aliases that had been applied
|
496
|
+
field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(field_tbl_name)
|
484
497
|
|
485
498
|
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
486
499
|
is_xml = is_distinct && Brick.relations[sel_col.first.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
|
@@ -737,7 +750,8 @@ Module.class_exec do
|
|
737
750
|
# Vabc instead of VABC)
|
738
751
|
full_class_name = +''
|
739
752
|
full_class_name << "::#{self.name}" unless self == Object
|
740
|
-
|
753
|
+
singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize
|
754
|
+
full_class_name << "::#{singular_class_name}"
|
741
755
|
if plural_class_name == 'BrickSwagger' ||
|
742
756
|
(
|
743
757
|
(::Brick.config.add_status || ::Brick.config.add_orphans) &&
|
@@ -751,9 +765,11 @@ Module.class_exec do
|
|
751
765
|
base_module == Object && # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing
|
752
766
|
(schema_name = [(singular_table_name = class_name.underscore),
|
753
767
|
(table_name = singular_table_name.pluralize),
|
754
|
-
class_name,
|
768
|
+
::Brick.is_oracle ? class_name.upcase : class_name,
|
755
769
|
(plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }&.camelize ||
|
756
770
|
(::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name))
|
771
|
+
return self.const_get(schema_name) if self.const_defined?(schema_name)
|
772
|
+
|
757
773
|
# Build out a module for the schema if it's namespaced
|
758
774
|
# schema_name = schema_name.camelize
|
759
775
|
base_module.const_set(schema_name.to_sym, (built_module = Module.new))
|
@@ -1120,64 +1136,63 @@ class Object
|
|
1120
1136
|
end
|
1121
1137
|
return [new_controller_class, code + "end # BrickGem controller\n"]
|
1122
1138
|
when 'BrickSwagger'
|
1123
|
-
is_swagger = true
|
1139
|
+
is_swagger = true
|
1124
1140
|
end
|
1125
1141
|
|
1126
1142
|
self.protect_from_forgery unless: -> { self.request.format.js? }
|
1127
1143
|
self.define_method :index do
|
1128
|
-
# We do all of this now so that bt_descrip and hm_counts are available on the model early in case the user
|
1129
|
-
# wants to do an ORDER BY based on any of that
|
1130
|
-
translations = {}
|
1131
|
-
join_array = ::Brick::JoinArray.new
|
1132
|
-
is_add_bts = is_add_hms = true
|
1133
|
-
# This builds out bt_descrip and hm_counts on the model
|
1134
|
-
model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
|
1135
|
-
|
1136
1144
|
if is_swagger
|
1137
|
-
|
1145
|
+
if !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)
|
1154
|
+
json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
|
1138
1155
|
'servers': [
|
1139
|
-
{ 'url': '
|
1156
|
+
{ 'url': '{scheme}://{defaultHost}', 'variables': {
|
1157
|
+
'scheme': { 'default': request.env['rack.url_scheme'] },
|
1158
|
+
'defaultHost': { 'default': request.env['HTTP_HOST'] }
|
1159
|
+
} }
|
1140
1160
|
]
|
1141
1161
|
}
|
1142
|
-
json['paths'] = relations.inject({}) do |s,
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
'
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
'responses': { '200': { 'description': 'successful' } }
|
1162
|
+
json['paths'] = relations.inject({}) do |s, relation|
|
1163
|
+
unless ::Brick.config.enable_api == false
|
1164
|
+
table_description = relation.last[:description]
|
1165
|
+
s["#{::Brick.config.api_root}#{relation.first.tr('.', '/')}"] = {
|
1166
|
+
'get': {
|
1167
|
+
'summary': "list #{relation.first}",
|
1168
|
+
'description': table_description,
|
1169
|
+
'parameters': relation.last[:cols].map do |k, v|
|
1170
|
+
param = { 'name' => k, 'schema': { 'type': v.first } }
|
1171
|
+
if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
|
1172
|
+
param['description'] = col_descrip
|
1173
|
+
end
|
1174
|
+
param
|
1175
|
+
end,
|
1176
|
+
'responses': { '200': { 'description': 'successful' } }
|
1177
|
+
}
|
1159
1178
|
}
|
1160
|
-
|
1161
|
-
#
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
# }
|
1178
|
-
# ],
|
1179
|
-
}
|
1180
|
-
s
|
1179
|
+
|
1180
|
+
s["#{::Brick.config.api_root}#{relation.first.tr('.', '/')}/{id}"] = {
|
1181
|
+
'patch': {
|
1182
|
+
'summary': "update a #{relation.first.singularize}",
|
1183
|
+
'description': table_description,
|
1184
|
+
'parameters': relation.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
|
1185
|
+
param = { 'name' => k, 'schema': { 'type': v.first } }
|
1186
|
+
if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
|
1187
|
+
param['description'] = col_descrip
|
1188
|
+
end
|
1189
|
+
param
|
1190
|
+
end,
|
1191
|
+
'responses': { '200': { 'description': 'successful' } }
|
1192
|
+
}
|
1193
|
+
} unless relation.last.fetch(:isView, nil)
|
1194
|
+
s
|
1195
|
+
end
|
1181
1196
|
end
|
1182
1197
|
render inline: json.to_json, content_type: request.format
|
1183
1198
|
return
|
@@ -1185,6 +1200,14 @@ class Object
|
|
1185
1200
|
|
1186
1201
|
# Normal (non-swagger) request
|
1187
1202
|
|
1203
|
+
# We do all of this now so that bt_descrip and hm_counts are available on the model early in case the user
|
1204
|
+
# wants to do an ORDER BY based on any of that
|
1205
|
+
translations = {}
|
1206
|
+
join_array = ::Brick::JoinArray.new
|
1207
|
+
is_add_bts = is_add_hms = true
|
1208
|
+
# This builds out bt_descrip and hm_counts on the model
|
1209
|
+
model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
|
1210
|
+
|
1188
1211
|
# %%% Allow params to define which columns to use for order_by
|
1189
1212
|
# Overriding the default by providing a querystring param?
|
1190
1213
|
ordering = params['_brick_order']&.split(',')&.map(&:to_sym) || Object.send(:default_ordering, table_name, pk)
|
@@ -1198,8 +1221,9 @@ class Object
|
|
1198
1221
|
end
|
1199
1222
|
render inline: exported_csv, content_type: request.format
|
1200
1223
|
return
|
1201
|
-
elsif request.format == :js # Asking for JSON?
|
1202
|
-
|
1224
|
+
elsif request.format == :js || request.path.start_with?('/api/') # Asking for JSON?
|
1225
|
+
data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
|
1226
|
+
render inline: data.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
|
1203
1227
|
return
|
1204
1228
|
end
|
1205
1229
|
|
@@ -1215,7 +1239,7 @@ class Object
|
|
1215
1239
|
"b_r_#{v.first}.c_t_ AS \"b_r_#{v.first}_ct\""
|
1216
1240
|
end
|
1217
1241
|
end
|
1218
|
-
instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
1242
|
+
instance_variable_set("@#{table_name.pluralize}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
1219
1243
|
if namespace && (idx = lookup_context.prefixes.index(table_name))
|
1220
1244
|
lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
|
1221
1245
|
end
|
@@ -1230,32 +1254,34 @@ class Object
|
|
1230
1254
|
@_brick_erd = params['_brick_erd']&.to_i
|
1231
1255
|
end
|
1232
1256
|
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
is_pk_string = nil
|
1240
|
-
if (pk_col = model&.primary_key)
|
1241
|
-
code << " def show\n"
|
1242
|
-
code << " #{find_by_name = "find_#{singular_table_name}"}\n"
|
1257
|
+
unless is_swagger
|
1258
|
+
::Brick.set_db_schema
|
1259
|
+
_, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk)) if pk
|
1260
|
+
code << " def index\n"
|
1261
|
+
code << " @#{table_name.pluralize} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
1262
|
+
code << " @#{table_name.pluralize}.brick_select(params)\n"
|
1243
1263
|
code << " end\n"
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1264
|
+
|
1265
|
+
is_pk_string = nil
|
1266
|
+
if pk.present?
|
1267
|
+
code << " def show\n"
|
1268
|
+
code << " #{find_by_name = "find_#{singular_table_name}"}\n"
|
1269
|
+
code << " end\n"
|
1270
|
+
self.define_method :show do
|
1271
|
+
::Brick.set_db_schema(params)
|
1272
|
+
id = if model.columns_hash[pk.first]&.type == :string
|
1273
|
+
is_pk_string = true
|
1274
|
+
params[:id]
|
1275
|
+
else
|
1276
|
+
params[:id]&.split(/[\/,_]/)
|
1277
|
+
end
|
1278
|
+
id = id.first if id.is_a?(Array) && id.length == 1
|
1279
|
+
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1280
|
+
end
|
1254
1281
|
end
|
1255
|
-
end
|
1256
1282
|
|
1257
|
-
|
1258
|
-
|
1283
|
+
# By default, views get marked as read-only
|
1284
|
+
# unless model.readonly # (relation = relations[model.table_name]).key?(:isView)
|
1259
1285
|
code << " def new\n"
|
1260
1286
|
code << " @#{singular_table_name} = #{model.name}.new\n"
|
1261
1287
|
code << " end\n"
|
@@ -1289,7 +1315,7 @@ class Object
|
|
1289
1315
|
end
|
1290
1316
|
end
|
1291
1317
|
|
1292
|
-
if
|
1318
|
+
if pk.present?
|
1293
1319
|
# if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
|
1294
1320
|
# ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
1295
1321
|
# end
|
@@ -1336,9 +1362,9 @@ class Object
|
|
1336
1362
|
end
|
1337
1363
|
end
|
1338
1364
|
|
1339
|
-
code << "private\n" if
|
1365
|
+
code << "private\n" if pk.present? || is_need_params
|
1340
1366
|
|
1341
|
-
if
|
1367
|
+
if pk.present?
|
1342
1368
|
code << " def find_#{singular_table_name}
|
1343
1369
|
id = params[:id]&.split(/[\\/,_]/)
|
1344
1370
|
@#{singular_table_name} = #{model.name}.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
|
@@ -1366,16 +1392,16 @@ class Object
|
|
1366
1392
|
private params_name
|
1367
1393
|
# Get column names for params from relations[model.table_name][:cols].keys
|
1368
1394
|
end
|
1369
|
-
#
|
1395
|
+
end # unless is_swagger
|
1370
1396
|
code << "end # #{namespace_name}#{class_name}\n"
|
1371
1397
|
end # class definition
|
1372
1398
|
[built_controller, code]
|
1373
1399
|
end
|
1374
1400
|
|
1375
1401
|
def _brick_get_hm_assoc_name(relation, hm_assoc, source = nil)
|
1376
|
-
if (relation[:hm_counts][hm_assoc[:
|
1402
|
+
if (relation[:hm_counts][hm_assoc[:inverse_table]]&.> 1) &&
|
1377
1403
|
hm_assoc[:alternate_name] != (source || name.underscore)
|
1378
|
-
plural = ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])
|
1404
|
+
plural = "#{hm_assoc[:assoc_name]}_#{ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name])}"
|
1379
1405
|
new_alt_name = (hm_assoc[:alternate_name] == name.underscore) ? "#{hm_assoc[:assoc_name].singularize}_#{plural}" : plural
|
1380
1406
|
# uniq = 1
|
1381
1407
|
# while same_name = relation[:fks].find { |x| x.last[:assoc_name] == hm_assoc[:assoc_name] && x.last != hm_assoc }
|
@@ -1386,8 +1412,13 @@ class Object
|
|
1386
1412
|
[new_alt_name, true]
|
1387
1413
|
else
|
1388
1414
|
assoc_name = ::Brick.namify(hm_assoc[:inverse_table]).pluralize
|
1415
|
+
if (needs_class = assoc_name.include?('.')) # If there is a schema name present, use a downcased version for the :has_many
|
1416
|
+
assoc_parts = assoc_name.split('.')
|
1417
|
+
assoc_parts[0].downcase! if assoc_parts[0] =~ /^[A-Z0-9_]+$/
|
1418
|
+
assoc_name = assoc_parts.join('.')
|
1419
|
+
end
|
1389
1420
|
# hm_assoc[:assoc_name] = assoc_name
|
1390
|
-
[assoc_name,
|
1421
|
+
[assoc_name, needs_class]
|
1391
1422
|
end
|
1392
1423
|
end
|
1393
1424
|
end
|
@@ -1443,7 +1474,8 @@ module ActiveRecord::ConnectionHandling
|
|
1443
1474
|
row['table_schema']
|
1444
1475
|
end
|
1445
1476
|
# Remove any system schemas
|
1446
|
-
s[row] = nil unless ['information_schema', 'pg_catalog'
|
1477
|
+
s[row] = nil unless ['information_schema', 'pg_catalog',
|
1478
|
+
'INFORMATION_SCHEMA', 'sys'].include?(row)
|
1447
1479
|
end
|
1448
1480
|
if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
|
1449
1481
|
(sta = multitenancy[:schema_to_analyse]) != 'public') &&
|
@@ -1487,6 +1519,7 @@ module ActiveRecord::ConnectionHandling
|
|
1487
1519
|
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1488
1520
|
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
1489
1521
|
measures = []
|
1522
|
+
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
1490
1523
|
case ActiveRecord::Base.connection.adapter_name
|
1491
1524
|
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1492
1525
|
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
@@ -1499,6 +1532,8 @@ module ActiveRecord::ConnectionHandling
|
|
1499
1532
|
r['schema']
|
1500
1533
|
end
|
1501
1534
|
relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
|
1535
|
+
# Both uppers and lowers as well as underscores?
|
1536
|
+
apply_double_underscore_patch if relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
1502
1537
|
relation = relations[relation_name]
|
1503
1538
|
relation[:isView] = true if r['table_type'] == 'VIEW'
|
1504
1539
|
relation[:description] = r['table_description'] if r['table_description']
|
@@ -1515,6 +1550,7 @@ module ActiveRecord::ConnectionHandling
|
|
1515
1550
|
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
1516
1551
|
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
|
1517
1552
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
1553
|
+
relation[:col_descrips][col_name] = r['column_description'] if r['column_description']
|
1518
1554
|
end
|
1519
1555
|
else # MySQL2 and OracleEnhanced act a little differently, bringing back an array for each row
|
1520
1556
|
schema_and_tables = case ActiveRecord::Base.connection.adapter_name
|
@@ -1538,15 +1574,24 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1538
1574
|
else
|
1539
1575
|
ActiveRecord::Base.retrieve_schema_and_tables(sql)
|
1540
1576
|
end
|
1577
|
+
|
1541
1578
|
schema_and_tables.each do |r|
|
1542
1579
|
next if r[1].index('$') # Oracle can have goofy table names with $
|
1543
1580
|
|
1544
|
-
if (relation_name = r[1]) =~ /^[A-
|
1581
|
+
if (relation_name = r[1]) =~ /^[A-Z0-9_]+$/
|
1545
1582
|
relation_name.downcase!
|
1583
|
+
# Both uppers and lowers as well as underscores?
|
1584
|
+
elsif relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
1585
|
+
apply_double_underscore_patch
|
1546
1586
|
end
|
1587
|
+
# Expect the default schema for SQL Server to be 'dbo'.
|
1588
|
+
if (::Brick.is_oracle && r[0] != schema) || (is_mssql && r[0] != 'dbo')
|
1589
|
+
relation_name = "#{r[0]}.#{relation_name}"
|
1590
|
+
end
|
1591
|
+
|
1547
1592
|
relation = relations[relation_name] # here relation represents a table or view from the database
|
1548
1593
|
relation[:isView] = true if r[2] == 'VIEW' # table_type
|
1549
|
-
col_name = r[3]
|
1594
|
+
col_name = ::Brick.is_oracle ? connection.send(:oracle_downcase, r[3]) : r[3]
|
1550
1595
|
key = case r[6] # constraint type
|
1551
1596
|
when 'PRIMARY KEY'
|
1552
1597
|
# key
|
@@ -1558,8 +1603,8 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1558
1603
|
end
|
1559
1604
|
key << col_name if key
|
1560
1605
|
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
1561
|
-
# 'data_type', 'max_length'
|
1562
|
-
cols[col_name] = [r[4], r[5], measures&.include?(col_name)]
|
1606
|
+
# 'data_type', 'max_length', measure, 'is_nullable'
|
1607
|
+
cols[col_name] = [r[4], r[5], measures&.include?(col_name), r[8] == 'NO']
|
1563
1608
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
1564
1609
|
end
|
1565
1610
|
end
|
@@ -1612,10 +1657,10 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1612
1657
|
schemas = ::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')
|
1613
1658
|
sql =
|
1614
1659
|
"SELECT -- fk
|
1615
|
-
ac.owner AS constraint_schema, acc_fk.table_name,
|
1660
|
+
ac.owner AS constraint_schema, acc_fk.table_name, acc_fk.column_name,
|
1616
1661
|
-- referenced pk
|
1617
1662
|
ac.r_owner AS primary_schema, acc_pk.table_name AS primary_table, acc_fk.constraint_name AS constraint_schema_fk
|
1618
|
-
-- ,
|
1663
|
+
-- , acc_pk.column_name
|
1619
1664
|
FROM all_cons_columns acc_fk
|
1620
1665
|
INNER JOIN all_constraints ac ON acc_fk.owner = ac.owner
|
1621
1666
|
AND acc_fk.constraint_name = ac.constraint_name
|
@@ -1633,55 +1678,80 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1633
1678
|
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
1634
1679
|
if apartment_excluded&.include?(::Brick.namify(fk[1]).singularize.camelize)
|
1635
1680
|
fk[0] = Apartment.default_schema
|
1636
|
-
elsif is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema)) ||
|
1637
|
-
|
1681
|
+
elsif (is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema))) ||
|
1682
|
+
(::Brick.is_oracle && fk[0] == schema) ||
|
1683
|
+
(is_mssql && fk[0] == 'dbo') ||
|
1684
|
+
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
|
1638
1685
|
fk[0] = nil
|
1639
1686
|
end
|
1640
1687
|
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
1641
1688
|
fk[3] = Apartment.default_schema
|
1642
|
-
elsif is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema)) ||
|
1643
|
-
|
1689
|
+
elsif (is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema))) ||
|
1690
|
+
(::Brick.is_oracle && fk[3] == schema) ||
|
1691
|
+
(is_mssql && fk[3] == 'dbo') ||
|
1692
|
+
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
|
1644
1693
|
fk[3] = nil
|
1645
1694
|
end
|
1646
|
-
|
1647
|
-
|
1695
|
+
if ::Brick.is_oracle
|
1696
|
+
fk[1].downcase! if fk[1] =~ /^[A-Z0-9_]+$/
|
1697
|
+
fk[4].downcase! if fk[4] =~ /^[A-Z0-9_]+$/
|
1698
|
+
fk[2] = connection.send(:oracle_downcase, fk[2])
|
1699
|
+
end
|
1648
1700
|
::Brick._add_bt_and_hm(fk, relations)
|
1649
1701
|
end
|
1650
1702
|
end
|
1651
1703
|
|
1652
1704
|
tables = []
|
1653
1705
|
views = []
|
1706
|
+
table_class_length = 0
|
1707
|
+
view_class_length = 0
|
1654
1708
|
relations.each do |k, v|
|
1655
1709
|
name_parts = k.split('.')
|
1656
1710
|
idx = 1
|
1657
1711
|
name_parts = name_parts.map do |x|
|
1658
|
-
(
|
1712
|
+
(idx += 1) <= name_parts.length ? x : x.singularize
|
1659
1713
|
end
|
1714
|
+
name_parts.shift if apartment && name_parts.length > 1 && name_parts.first == Apartment.default_schema
|
1715
|
+
class_name = name_parts.map(&:camelize).join('::')
|
1660
1716
|
if v.key?(:isView)
|
1717
|
+
view_class_length = class_name.length if class_name.length > view_class_length
|
1661
1718
|
views
|
1662
1719
|
else
|
1663
|
-
|
1720
|
+
table_class_length = class_name.length if class_name.length > table_class_length
|
1664
1721
|
tables
|
1665
|
-
end << name_parts
|
1722
|
+
end << [class_name, name_parts]
|
1666
1723
|
end
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1724
|
+
puts "\n" if tables.present? || views.present?
|
1725
|
+
if tables.present?
|
1726
|
+
puts "Classes that can be built from tables:"
|
1727
|
+
display_classes(tables, table_class_length)
|
1670
1728
|
end
|
1671
|
-
|
1672
|
-
puts "
|
1673
|
-
views
|
1729
|
+
if views.present?
|
1730
|
+
puts "Classes that can be built from views:"
|
1731
|
+
display_classes(views, view_class_length)
|
1674
1732
|
end
|
1675
1733
|
|
1676
1734
|
::Brick.load_additional_references if initializer_loaded
|
1677
1735
|
end
|
1678
1736
|
|
1737
|
+
def display_classes(rels, max_length)
|
1738
|
+
rels.sort.each do |rel|
|
1739
|
+
rel_link = rel.last.dup.map(&:underscore)
|
1740
|
+
rel_link[-1] = rel_link[-1]
|
1741
|
+
puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{rel_link.join('/')}"
|
1742
|
+
end
|
1743
|
+
puts "\n"
|
1744
|
+
end
|
1745
|
+
|
1679
1746
|
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
|
1680
1747
|
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
|
1681
1748
|
sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
|
1682
1749
|
pg_catalog.obj_description(
|
1683
|
-
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass, 'pg_class'
|
1684
|
-
) AS table_description,
|
1750
|
+
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, 'pg_class'
|
1751
|
+
) AS table_description,
|
1752
|
+
pg_catalog.col_description(
|
1753
|
+
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, c.ordinal_position
|
1754
|
+
) AS column_description," if is_postgres}
|
1685
1755
|
c.column_name, c.data_type,
|
1686
1756
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
1687
1757
|
kcu.constraint_type AS const, kcu.constraint_name AS \"key\",
|
@@ -1705,7 +1775,8 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1705
1775
|
-- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}
|
1706
1776
|
AND kcu.ordinal_position = c.ordinal_position
|
1707
1777
|
WHERE t.table_schema #{is_postgres || is_mssql ?
|
1708
|
-
"NOT IN ('information_schema', 'pg_catalog'
|
1778
|
+
"NOT IN ('information_schema', 'pg_catalog',
|
1779
|
+
'INFORMATION_SCHEMA', 'sys')"
|
1709
1780
|
:
|
1710
1781
|
"= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
|
1711
1782
|
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }
|
@@ -1724,6 +1795,43 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
1724
1795
|
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
|
1725
1796
|
[ar_smtn, ar_imtn]
|
1726
1797
|
end
|
1798
|
+
|
1799
|
+
def apply_double_underscore_patch
|
1800
|
+
unless @double_underscore_applied
|
1801
|
+
# Same as normal #camelize and #underscore, just that double-underscores turn into a single underscore
|
1802
|
+
ActiveSupport::Inflector.class_eval do
|
1803
|
+
def camelize(term, uppercase_first_letter = true)
|
1804
|
+
strings = term.to_s.split('__').map do |string|
|
1805
|
+
# String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent.
|
1806
|
+
if !uppercase_first_letter || uppercase_first_letter == :lower
|
1807
|
+
string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match }
|
1808
|
+
else
|
1809
|
+
string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match }
|
1810
|
+
end
|
1811
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
|
1812
|
+
word = $2
|
1813
|
+
substituted = inflections.acronyms[word] || word.capitalize! || word
|
1814
|
+
$1 ? "::#{substituted}" : substituted
|
1815
|
+
end
|
1816
|
+
string
|
1817
|
+
end
|
1818
|
+
strings.join('_')
|
1819
|
+
end
|
1820
|
+
|
1821
|
+
def underscore(camel_cased_word)
|
1822
|
+
return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)
|
1823
|
+
camel_cased_word.to_s.gsub("::", "/").split('_').map do |word|
|
1824
|
+
word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
|
1825
|
+
word.gsub!(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }
|
1826
|
+
word.tr!("-", "_")
|
1827
|
+
word.downcase!
|
1828
|
+
word
|
1829
|
+
end.join('__')
|
1830
|
+
end
|
1831
|
+
end
|
1832
|
+
@double_underscore_applied = true
|
1833
|
+
end
|
1834
|
+
end
|
1727
1835
|
end
|
1728
1836
|
|
1729
1837
|
# ==========================================
|
@@ -1750,7 +1858,7 @@ module Brick
|
|
1750
1858
|
|
1751
1859
|
class << self
|
1752
1860
|
def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
|
1753
|
-
bt_assoc_name = ::Brick.namify(fk[2])
|
1861
|
+
bt_assoc_name = ::Brick.namify(fk[2], :downcase)
|
1754
1862
|
unless is_polymorphic
|
1755
1863
|
bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
|
1756
1864
|
bt_assoc_name[-3] == '_' ? bt_assoc_name[0..-4] : bt_assoc_name[0..-3]
|
@@ -1851,6 +1959,11 @@ module Brick
|
|
1851
1959
|
|
1852
1960
|
return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[1] == exclusion[0] && fk[2] == exclusion[1] && primary_table == exclusion[2] } || hms.nil?
|
1853
1961
|
|
1962
|
+
# if fk[1].end_with?('Suppliers') && fk[4] == 'People'
|
1963
|
+
# puts fk.inspect
|
1964
|
+
# binding.pry
|
1965
|
+
# end
|
1966
|
+
|
1854
1967
|
if (assoc_hm = hms.fetch((hm_cnstr_name = "hm_#{cnstr_name}"), nil))
|
1855
1968
|
if assoc_hm[:fk].is_a?(String)
|
1856
1969
|
assoc_hm[:fk] = [assoc_hm[:fk], fk[2]] unless fk[2] == assoc_hm[:fk]
|
@@ -1858,7 +1971,6 @@ module Brick
|
|
1858
1971
|
assoc_hm[:fk] << fk[2]
|
1859
1972
|
end
|
1860
1973
|
assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
|
1861
|
-
assoc_hm[:inverse] = assoc_bt
|
1862
1974
|
else
|
1863
1975
|
inv_tbl = if ::Brick.config.schema_behavior[:multitenant] && apartment && fk[0] == Apartment.default_schema
|
1864
1976
|
for_tbl
|
@@ -1869,7 +1981,7 @@ module Brick
|
|
1869
1981
|
inverse_table: inv_tbl, inverse: assoc_bt }
|
1870
1982
|
assoc_hm[:polymorphic] = true if is_polymorphic
|
1871
1983
|
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
1872
|
-
hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
|
1984
|
+
this_hm_count = hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
|
1873
1985
|
end
|
1874
1986
|
assoc_bt[:inverse] = assoc_hm
|
1875
1987
|
end
|