brick 1.0.93 → 1.0.95

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 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