brick 1.0.93 → 1.0.95

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8488acac0623c04f11e916c67303fd06d471154231c92613318cc7458fcfe23f
4
- data.tar.gz: af566c853bb25e7497a6afbba539ad0d14ec23ad000c053e6dbdf757deefbaa0
3
+ metadata.gz: ebb01aca7fcf3fa8cfbb699e48fa85601341df79645d4f3d6ebc5a43351c3d29
4
+ data.tar.gz: 2bca81242ee8eaa5bbfd477b39df8eb969f8999abe680329e2ae9b1b4ef928a7
5
5
  SHA512:
6
- metadata.gz: d0d8428b6f80bf3a79abb16265fb8625404f92caf30034da94186a5c5982a78543f29e0a49cc0dc515eb8b6d6da24ccdd53e8d089c90dce46ce214ce5bee4339
7
- data.tar.gz: 562bd7522f0f45843a89464645e1cc153e455e1e5b4e3dec5f1b38a8bfaefd8bd359a2538aa274b90b50353285d2175f315b7e304e54e60516a5ce2ed3b2fc7e
6
+ metadata.gz: 566f2e6925b918fb8c7b3bf545dc8fd98f840b35fdf875e0f7812de3f127dae21e2fd4b9a2f3e9faba5255ec83d76b0651657e0051aa8e9706429354dee4d012
7
+ data.tar.gz: b07ce93b35464f516e3f03b1c8c98d215b3fa2db1051af5d6d41131313d694e483cc7cc40a063b6fd6b70e6d5eb2afb99fb9cc47914a81e6dc0a2392a5b2602c
data/lib/brick/config.rb CHANGED
@@ -303,5 +303,13 @@ module Brick
303
303
  def add_orphans
304
304
  true
305
305
  end
306
+
307
+ def license_key
308
+ @mutex.synchronize { @license_key }
309
+ end
310
+
311
+ def license_key=(key)
312
+ @mutex.synchronize { @license_key = key }
313
+ end
306
314
  end
307
315
  end
@@ -245,12 +245,15 @@ module ActiveRecord
245
245
  end
246
246
 
247
247
  def self.bt_link(assoc_name)
248
- assoc_name = CGI.escapeHTML(assoc_name.to_s)
248
+ assoc_html_name = unless (assoc_name = assoc_name.to_s).camelize == name
249
+ CGI.escapeHTML(assoc_name)
250
+ end
249
251
  model_path = ::Rails.application.routes.url_helpers.send("#{_brick_index}_path".to_sym)
252
+ model_path << "?#{self.inheritance_column}=#{self.name}" if self != base_class
250
253
  av_class = Class.new.extend(ActionView::Helpers::UrlHelper)
251
254
  av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('7')
252
- link = av_class.link_to(name, model_path)
253
- table_name == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
255
+ link = av_class.link_to(assoc_html_name ? name : assoc_name, model_path)
256
+ assoc_html_name ? "#{assoc_name}-#{link}".html_safe : link
254
257
  end
255
258
 
256
259
  def self._brick_index(mode = nil)
@@ -542,7 +545,7 @@ module ActiveRecord
542
545
  field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(field_tbl_name)
543
546
 
544
547
  # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
545
- is_xml = is_distinct && Brick.relations[field_tbl_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
548
+ is_xml = is_distinct && Brick.relations[k1.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
546
549
  # If it's not unique then also include the belongs_to association name before the column name
547
550
  if used_col_aliases.key?(col_alias = "br_fk_#{v.first}__#{sel_col.last}")
548
551
  col_alias = "br_fk_#{v.first}__#{v1[idx][-2..-1].map(&:to_s).join('__')}"
@@ -1413,6 +1416,85 @@ class Object
1413
1416
  self.define_method :orphans do
1414
1417
  instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params).first))
1415
1418
  end
1419
+ self.define_method :crosstab do
1420
+ @relations = ::Brick.relations.each_with_object({}) do |r, s|
1421
+ cols = r.last[:cols].each_with_object([]) do |c, s2|
1422
+ s2 << [c.first] + c.last
1423
+ end
1424
+ s[r.first] = { cols: cols } if r.last.key?(:cols)
1425
+ end
1426
+ end
1427
+
1428
+ self.define_method :crosstab_data do
1429
+ # Bring in column names and use this to create an appropriate SELECT statement
1430
+ is_grouped = nil
1431
+ fields_in_sequence = params['fields'].split(',')
1432
+ relations = {}
1433
+ first_relation = nil
1434
+ fields = fields_in_sequence.each_with_object(Hash.new { |h, k| h[k] = {} }) do |f, s|
1435
+ relation, col = if (paren_index = f.index('(')) # Aggregate?
1436
+ aggregate = f[0..paren_index - 1]
1437
+ f_parts = f[(paren_index + 1)..-2].split(',')
1438
+ aggregate_options = f_parts[1..-1] # Options about aggregation
1439
+ f_parts.first.split('/') # Column being aggregated
1440
+ else
1441
+ f.split('/')
1442
+ end
1443
+ first_relation ||= relation
1444
+ # relation = if dts[(relation = relation.downcase)]
1445
+ # relation # Generally a common JOIN point, but who knows, maybe we'll add more
1446
+ # else
1447
+ # relation
1448
+ # end
1449
+ if col
1450
+ relations[relation] = nil
1451
+ s[f] = if aggregate
1452
+ is_grouped = true
1453
+ [aggregate, relation, col, aggregate_options]
1454
+ else
1455
+ [relation, col]
1456
+ end
1457
+ end
1458
+ s
1459
+ end
1460
+ # ver = params['ver']
1461
+ if fields.empty?
1462
+ render json: { data: [] } # [ver, []]
1463
+ return
1464
+ end
1465
+
1466
+ # Apartment::Tenant.switch!(params['schema'])
1467
+ # result = ActiveRecord::Base.connection.query("SELECT #{cols.join(', ')} FROM #{view}")
1468
+ col_num = 0
1469
+ grouping = []
1470
+ cols = fields_in_sequence.each_with_object([]) do |f, s|
1471
+ c = fields[f]
1472
+ col_num += 1
1473
+ col_def = if c.length > 2 # Aggregate?
1474
+ case c.first
1475
+ when 'COMMA_SEP'
1476
+ "STRING_AGG(DISTINCT #{c[1].downcase}.\"#{c[2]}\"::varchar, ',')" # Like STRING_AGG(DISTINCT v_tacos."price"::varchar, ',')
1477
+ when 'COUNT_DISTINCT'
1478
+ "COUNT(DISTINCT #{c[1].downcase}.\"#{c[2]}\")" # Like COUNT(DISTINCT v_tacos."price")
1479
+ when 'MODE'
1480
+ "MODE() WITHIN GROUP(ORDER BY #{c[1].downcase}.\"#{c[2]}\")" # Like MODE() WITHIN GROUP(ORDER BY v_tacos."price")
1481
+ when 'NUM_DAYS'
1482
+ "EXTRACT(DAYS FROM (MAX(#{c[1].downcase}.\"#{c[2]}\")::timestamp - MIN(#{c[1].downcase}.\"#{c[2]}\")::timestamp))" # Like EXTRACT(DAYS FROM (MAX(order."order_date") - MIN(order."order_date"))
1483
+ else
1484
+ "#{c.first}(#{c[1].downcase}.\"#{c[2]}\")" # Something like AVG(v_tacos."price")
1485
+ end
1486
+ else # Normal column, represented in an array having: [relation, column_name]
1487
+ grouping << col_num
1488
+ "#{c.first.downcase}.\"#{c.last}\"" # Like v_tacos."price"
1489
+ end
1490
+ s << "#{col_def} AS c#{col_num}"
1491
+ end
1492
+ sql = "SELECT #{cols.join(', ')} FROM #{first_relation.downcase}"
1493
+ sql << "\nGROUP BY #{grouping.map(&:to_s).join(',')}" if is_grouped && grouping.present?
1494
+ result = ActiveRecord::Base.connection.query(sql)
1495
+ render json: { data: result } # [ver, result]
1496
+ end
1497
+
1416
1498
  return [new_controller_class, code + "end # BrickGem controller\n"]
1417
1499
  when 'BrickOpenapi'
1418
1500
  is_openapi = true
@@ -2310,7 +2392,7 @@ module Brick
2310
2392
  rails_root = ::Rails.root.to_s
2311
2393
  migrations = if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{rails_root}/db/migrate")
2312
2394
  Dir["#{mig_path}/**/*.rb"].each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
2313
- File.read(v).split("\n").each do |line|
2395
+ File.read(v).split("\n").each_with_index do |line, line_idx|
2314
2396
  # For all non-commented lines, look for any that have "create_table", "alter_table", or "drop_table"
2315
2397
  if !line.lstrip.start_with?('#') &&
2316
2398
  (idx = (line.index('create_table ') || line.index('create_table('))&.+(13)) ||
@@ -2318,8 +2400,9 @@ module Brick
2318
2400
  (idx = (line.index('drop_table ') || line.index('drop_table('))&.+(11))
2319
2401
  tbl = line[idx..-1].match(/([:'"\w\.]+)/)&.captures&.first
2320
2402
  if tbl
2321
- s[tbl.tr(':\'"', '').pluralize] << v
2322
- break
2403
+ v = v[(rails_root.length)..-1] if v.start_with?(rails_root)
2404
+ v = v[1..-1] if v.start_with?('/')
2405
+ s[tbl.tr(':\'"', '').pluralize] << [v, line_idx + 1]
2323
2406
  end
2324
2407
  end
2325
2408
  end
@@ -71,6 +71,7 @@ module Brick
71
71
  def template_exists?(*args, **options)
72
72
  (::Brick.config.add_status && args.first == 'status') ||
73
73
  (::Brick.config.add_orphans && args.first == 'orphans') ||
74
+ (args.first == 'crosstab') ||
74
75
  _brick_template_exists?(*args, **options) ||
75
76
  # Do not auto-create a template when it's searching for an application.html.erb, which comes in like: ["edit", ["games", "application"]]
76
77
  ((args[1].length == 1 || args[1][-1] != 'application') &&
@@ -107,7 +108,8 @@ module Brick
107
108
  def find_template(*args, **options)
108
109
  unless (model_name = @_brick_model&.name) ||
109
110
  (is_status = ::Brick.config.add_status && args[0..1] == ['status', ['brick_gem']]) ||
110
- (is_orphans = ::Brick.config.add_orphans && args[0..1] == ['orphans', ['brick_gem']])
111
+ (is_orphans = ::Brick.config.add_orphans && args[0..1] == ['orphans', ['brick_gem']]) ||
112
+ (is_crosstab = args[0..1] == ['crosstab', ['brick_gem']])
111
113
  if ActionView.version < ::Gem::Version.new('5.0') # %%% Not sure if this should be perhaps 4.2 instead
112
114
  begin
113
115
  if (possible_template = _brick_find_template(*args, **options))
@@ -141,10 +143,10 @@ module Brick
141
143
  next unless @_brick_model.instance_methods.include?(through) &&
142
144
  (associative = @_brick_model._br_associatives.fetch(hm.first, nil))
143
145
 
144
- tbl_nm = if (source = hm_assoc.source_reflection).macro == :belongs_to
145
- hm_assoc.through_reflection&.name # for standard HMT, which is HM -> BT
146
- else
146
+ tbl_nm = if (source = hm_assoc.source_reflection).macro == :has_many
147
147
  source.inverse_of&.name # For HM -> HM style HMT
148
+ else # belongs_to or has_one
149
+ hm_assoc.through_reflection&.name # for standard HMT, which is HM -> BT
148
150
  end
149
151
  # If there is no inverse available for the source belongs_to association, make one based on the class name
150
152
  unless tbl_nm
@@ -203,7 +205,6 @@ module Brick
203
205
  # environment or whatever, then get either the controllers or routes list instead
204
206
  prefix = "#{::Brick.config.path_prefix}/" if ::Brick.config.path_prefix
205
207
  table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).each_with_object({}) do |tbl, s|
206
- binding.pry if tbl.is_a?(Symbol)
207
208
  if (tbl_parts = tbl.split('.')).first == apartment_default_schema
208
209
  tbl = tbl_parts.last
209
210
  end
@@ -213,6 +214,7 @@ module Brick
213
214
  end.html_safe
214
215
  table_options << "<option value=\"#{prefix}brick_status\">(Status)</option>".html_safe if ::Brick.config.add_status
215
216
  table_options << "<option value=\"#{prefix}brick_orphans\">(Orphans)</option>".html_safe if is_orphans
217
+ table_options << "<option value=\"#{prefix}brick_orphans\">(Crosstab)</option>".html_safe if is_crosstab
216
218
  css = +"<style>
217
219
  h1, h3 {
218
220
  margin-bottom: 0;
@@ -911,7 +913,9 @@ erDiagram
911
913
  end
912
914
  unless @_brick_sequence # If no sequence is defined, start with all inclusions
913
915
  cust_cols = #{model_name}._br_cust_cols
914
- @_brick_sequence = col_keys + cust_cols.keys + #{(hms_keys).inspect}.reject { |assoc_name| @_brick_incl&.exclude?(assoc_name) }
916
+ # HOT columns, kept as symbols
917
+ hots = #{model_name}._br_bt_descrip.keys.select { |k| bts.key?(k) }
918
+ @_brick_sequence = col_keys + cust_cols.keys + hots + #{(hms_keys).inspect}.reject { |assoc_name| @_brick_incl&.exclude?(assoc_name) }
915
919
  end
916
920
  @_brick_sequence.reject! { |nm| @_brick_excl.include?(nm) } if @_brick_excl # Reject exclusions
917
921
  @_brick_sequence.each_with_object(+'') do |col_name, s|
@@ -928,8 +932,12 @@ erDiagram
928
932
  elsif col # HM column
929
933
  s << \"<th#\{' x-order=\"' + col_name + '\"' if true}>#\{col[2]} \"
930
934
  s << (col.first ? \"#\{col[3]}\" : \"#\{link_to(col[3], send(\"#\{col[1]._brick_index}_path\"))}\")
931
- elsif (cc = cust_cols.key?(col_name)) # Custom column
935
+ elsif cust_cols.key?(col_name) # Custom column
932
936
  s << \"<th x-order=\\\"#\{col_name}\\\">#\{col_name}\"
937
+ elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
938
+ s << \"<th x-order=\\\"#\{hot.first.to_s}\\\">HOT \" +
939
+ hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
940
+ hot[1].first
933
941
  else # Bad column name!
934
942
  s << \"<th title=\\\"<< Unknown column >>\\\">#\{col_name}\"
935
943
  end
@@ -947,14 +955,16 @@ erDiagram
947
955
  <td><%= link_to '⇛', #{path_obj_name}_path(slashify(#{obj_pk})), { class: 'big-arrow' } %></td>" if obj_pk}
948
956
  <% @_brick_sequence.each do |col_name|
949
957
  val = #{obj_name}.attributes[col_name] %>
950
- <td<%= ' class=\"dimmed\"'.html_safe unless cols.key?(col_name) || (cust_col = cust_cols[col_name])%>><%
958
+ <td<%= ' class=\"dimmed\"'.html_safe unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
959
+ (col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
960
+ %>><%
951
961
  if (bt = bts[col_name])
952
962
  if bt[2] # Polymorphic?
953
963
  bt_class = #{obj_name}.send(\"#\{bt.first}_type\")
954
964
  base_class_underscored = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class._brick_index(:singular)
955
965
  poly_id = #{obj_name}.send(\"#\{bt.first}_id\")
956
966
  %><%= link_to(\"#\{bt_class} ##\{poly_id}\", send(\"#\{base_class_underscored}_path\".to_sym, poly_id)) if poly_id %><%
957
- else
967
+ else # BT or HOT
958
968
  bt_class = bt[1].first.first
959
969
  descrips = @_brick_bt_descrip[bt.first][bt_class]
960
970
  bt_id_col = if descrips.nil?
@@ -1041,7 +1051,7 @@ erDiagram
1041
1051
  %>
1042
1052
  <tr>
1043
1053
  <td><%= begin
1044
- kls = Object.const_get(::Brick.relations[r[0]].fetch(:class_name, nil))
1054
+ kls = Object.const_get(::Brick.relations.fetch(r[0], nil)&.fetch(:class_name, nil))
1045
1055
  rescue
1046
1056
  end
1047
1057
  kls ? link_to(r[0], send(\"#\{kls._brick_index}_path\".to_sym)) : r[0] %></td>
@@ -1051,8 +1061,9 @@ erDiagram
1051
1061
  ' class=\"dimmed\"'
1052
1062
  end&.html_safe %>><%= # Table
1053
1063
  r[1] %></td>
1054
- <td<%= ' class=\"dimmed\"'.html_safe unless r[2] %>><%= # Migration
1055
- r[2]&.join('<br>')&.html_safe %></td>
1064
+ <td<%= lines = r[2]&.map { |line| \"#\{line.first}:#\{line.last}\" }
1065
+ ' class=\"dimmed\"'.html_safe unless r[2] %>><%= # Migration
1066
+ lines&.join('<br>')&.html_safe %></td>
1056
1067
  <td<%= ' class=\"dimmed\"'.html_safe unless r[3] %>><%= # Model
1057
1068
  r[3] %></td>
1058
1069
  <td<%= ' class=\"dimmed\"'.html_safe unless r[4] %>><%= # Route
@@ -1085,6 +1096,16 @@ erDiagram
1085
1096
  #{script}"
1086
1097
  end
1087
1098
 
1099
+ when 'crosstab'
1100
+ if is_crosstab && ::Brick.config.license_key
1101
+ decipher = OpenSSL::Cipher::AES256.new(:CBC).decrypt
1102
+ decipher.iv = "\xB4,\r2\x19\xF5\xFE/\aR\x1A\x8A\xCFV\v\x8C"
1103
+ decipher.key = Digest::SHA256.hexdigest(::Brick.config.license_key).scan(/../).map { |x| x.hex }.pack('c*')
1104
+ decipher.update(File.binread("/Users/aga/brick/lib/brick/frameworks/rails/crosstab.brk"))[16..-1]
1105
+ else
1106
+ 'Crosstab Charting not yet activated -- enter a valid license key in brick.rb'
1107
+ end
1108
+
1088
1109
  when 'show', 'new', 'update'
1089
1110
  +"<html>
1090
1111
  <head>
@@ -1274,7 +1295,8 @@ end}
1274
1295
  "
1275
1296
 
1276
1297
  end
1277
- inline << "
1298
+ unless is_crosstab
1299
+ inline << "
1278
1300
  <% if is_includes_dates %>
1279
1301
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css\">
1280
1302
  <style>
@@ -1408,6 +1430,7 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
1408
1430
  }
1409
1431
  });
1410
1432
  </script>"
1433
+ end
1411
1434
  # puts "==============="
1412
1435
  # puts inline
1413
1436
  # puts "==============="
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 93
8
+ TINY = 95
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -211,12 +211,13 @@ module Brick
211
211
  else
212
212
  s.first[a.foreign_key.to_s] = [a.name, a.klass]
213
213
  end
214
- else # This gets has_many as well as has_one and has_many :through
215
- if through
214
+ else # This gets all forms of has_many and has_one
215
+ if through # has_many :through or has_one :through
216
216
  is_invalid_source = nil
217
217
  begin
218
- if a.through_reflection&.belongs_to?
219
- puts "WARNING: HMT relationship :#{a.name} in model #{model.name} tries to go through belongs_to association :#{through}. This is not possible."
218
+ if a.through_reflection.macro != :has_many # This HM goes through either a belongs_to or a has_one, so essentially a HOT?
219
+ # Treat it like a belongs_to - just keyed on the association name instead of a foreign_key
220
+ s.first[a.name] = [a.name, a.klass]
220
221
  next
221
222
  elsif !a.source_reflection # Had considered: a.active_record.reflect_on_association(a.source_reflection_name).nil?
222
223
  is_invalid_source = true
@@ -465,6 +466,10 @@ module Brick
465
466
  Brick.config.default_route_fallback = resource_name
466
467
  end
467
468
 
469
+ def license_key=(key)
470
+ Brick.config.license_key = key
471
+ end
472
+
468
473
  # Load additional references (virtual foreign keys)
469
474
  # This is attempted early if a brick initialiser file is found, and then again as a failsafe at the end of our engine's initialisation
470
475
  # %%% Maybe look for differences the second time 'round and just add new stuff instead of entirely deferring
@@ -690,6 +695,11 @@ In config/initializers/brick.rb appropriate entries would look something like:
690
695
  get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
691
696
  end
692
697
 
698
+ if instance_variable_get(:@set).named_routes.names.exclude?(:brick_crosstab)
699
+ get("/#{controller_prefix}brick_crosstab", to: 'brick_gem#crosstab', as: 'brick_crosstab')
700
+ get("/#{controller_prefix}brick_crosstab/data", to: 'brick_gem#crosstab_data')
701
+ end
702
+
693
703
  unless ::Brick.routes_done
694
704
  if Object.const_defined?('Rswag::Ui')
695
705
  rswag_path = ::Rails.application.routes.routes.find { |r| r.app.app == Rswag::Ui::Engine }&.instance_variable_get(:@path_formatter)&.instance_variable_get(:@parts)&.join
@@ -1247,6 +1257,67 @@ module ActiveRecord
1247
1257
  # For AR >= 4.2
1248
1258
  if self.const_defined?('JoinDependency')
1249
1259
  class JoinDependency
1260
+ # An intelligent .eager_load() and .includes() that creates t0_r0 style aliases only for the columns
1261
+ # used in .select(). To enable this behaviour, include the flag :_brick_eager_load as the first
1262
+ # entry in your .select().
1263
+ # More information: https://discuss.rubyonrails.org/t/includes-and-select-for-joined-data/81640
1264
+ def apply_column_aliases(relation)
1265
+ used_cols = {}
1266
+ if (sel_vals = relation.select_values.map(&:to_s)).first == '_brick_eager_load'
1267
+ # Find and expand out all column names being used in select(...)
1268
+ new_select_values = sel_vals.each_with_object([]) do |col, s|
1269
+ next if col == '_brick_eager_load'
1270
+
1271
+ if col.include?(' ') # Some expression? (No chance for a simple column reference)
1272
+ s << col # Just pass it through
1273
+ else
1274
+ col = if (col_parts = col.split('.')).length == 1
1275
+ [col]
1276
+ else
1277
+ [col_parts[0..-2].join('.'), col_parts.last]
1278
+ end
1279
+ used_cols[col] = nil
1280
+ end
1281
+ end
1282
+ if new_select_values.present?
1283
+ relation.select_values = new_select_values
1284
+ else
1285
+ relation.select_values.clear
1286
+ end
1287
+ end
1288
+
1289
+ @aliases ||= Aliases.new(join_root.each_with_index.map do |join_part, i|
1290
+ join_alias = join_part.table&.table_alias || join_part.table_name
1291
+ keys = [join_part.base_klass.primary_key] # Always include the primary key
1292
+
1293
+ # # %%% Optional to include all foreign keys:
1294
+ # keys.concat(join_part.base_klass.reflect_on_all_associations.select { |a| a.belongs_to? }.map(&:foreign_key))
1295
+
1296
+ # Add foreign keys out to referenced tables that we belongs_to
1297
+ join_part.children.each { |child| keys << child.reflection.foreign_key if child.reflection.belongs_to? }
1298
+
1299
+ # Add the foreign key that got us here -- "the train we rode in on" -- if we arrived from
1300
+ # a has_many or has_one:
1301
+ if join_part.is_a?(ActiveRecord::Associations::JoinDependency::JoinAssociation) &&
1302
+ !join_part.reflection.belongs_to?
1303
+ keys << join_part.reflection.foreign_key
1304
+ end
1305
+ keys = keys.compact # In case we're using composite_primary_keys
1306
+ j = 0
1307
+ columns = join_part.column_names.each_with_object([]) do |column_name, s|
1308
+ # Include columns chosen in select(...) as well as the PK and any relevant FKs
1309
+ if used_cols.keys.find { |c| (c.length == 1 || c.first == join_alias) && c.last == column_name } ||
1310
+ keys.find { |c| c == column_name }
1311
+ s << Aliases::Column.new(column_name, "t#{i}_r#{j}")
1312
+ end
1313
+ j += 1
1314
+ end
1315
+ Aliases::Table.new(join_part, columns)
1316
+ end)
1317
+
1318
+ relation._select!(-> { @aliases.columns })
1319
+ end
1320
+
1250
1321
  private
1251
1322
 
1252
1323
  # %%% Pretty much have to flat-out replace this guy (I think anyway)
@@ -1282,7 +1353,6 @@ module ActiveRecord
1282
1353
  if (relation = node.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
1283
1354
  relation.brick_links[link_path] = result.first.table_alias || result.first.table_name
1284
1355
  end
1285
-
1286
1356
  result
1287
1357
  end
1288
1358
  else # Same idea but for Rails 7
@@ -1292,9 +1362,9 @@ module ActiveRecord
1292
1362
 
1293
1363
  # Capture the table alias name that was chosen
1294
1364
  link_path = child.instance_variable_get(:@link_path)
1295
- relation = child.instance_variable_get(:@assocs)&.instance_variable_get(:@relation)
1296
- # binding.pry if relation
1297
- relation.brick_links[link_path] = result.first.left.table_alias || result.first.left.table_name
1365
+ if (relation = child.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
1366
+ relation.brick_links[link_path] = result.first.left.table_alias || result.first.left.table_name
1367
+ end
1298
1368
  result
1299
1369
  end
1300
1370
  end
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.93
4
+ version: 1.0.95
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-11-21 00:00:00.000000000 Z
11
+ date: 2022-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -214,6 +214,7 @@ files:
214
214
  - lib/brick/frameworks/cucumber.rb
215
215
  - lib/brick/frameworks/rails.rb
216
216
  - lib/brick/frameworks/rails/controller.rb
217
+ - lib/brick/frameworks/rails/crosstab.brk
217
218
  - lib/brick/frameworks/rails/engine.rb
218
219
  - lib/brick/frameworks/rspec.rb
219
220
  - lib/brick/join_array.rb