brick 1.0.94 → 1.0.96

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: 4b4de74edca4a5dd2c7d57d23dda2aa4cbc966402db4f2d0490dfeb741224dd5
4
- data.tar.gz: fe905ed82bf8ae8f249e5ce3fe1646cb6366caf3343086ea8ba597e51525f521
3
+ metadata.gz: 0061ad25b22deee43d1939b6541257924a61a67b3b8b004fb6a0072302746ca2
4
+ data.tar.gz: 99968b9de834f73c60842bf5da03b5bddd750f1ff8c3ac1dac294078f0eb2358
5
5
  SHA512:
6
- metadata.gz: 45d2dda76345ab043c4e62d92b5e12a466ffec892e1ce63b540bd6427fcecfa777739732754d554f78b5803599fca1390fa2d38cb8f0acd9b0ffd8e5c6dd34aa
7
- data.tar.gz: 65b73535a5874fe5b3c46fec455fe62857259c7d56b68241a729f2c928a47e0fdcc71988a3b7ead506de717fae71932007855efaf18dd8866b920da2c6d5c779
6
+ metadata.gz: 4958e2b0c18522be75134ab29df481213594189b4addd6ef9368ae8677aba603cbb278176c57c51217a49c0042e98927cb33465995bc4aae01f3af59b2d64f15
7
+ data.tar.gz: 1d486886f30ce384edc7d84a441617906b26b001c7c3562558289eedfd4b0c12e2a860c587f71acb6e6727649a3756223f1240c07f4e1013029cfe5b67a9c117
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
308
+ @mutex.synchronize { @license }
309
+ end
310
+
311
+ def license=(key)
312
+ @mutex.synchronize { @license = key }
313
+ end
306
314
  end
307
315
  end
@@ -1416,6 +1416,85 @@ class Object
1416
1416
  self.define_method :orphans do
1417
1417
  instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params).first))
1418
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
+
1419
1498
  return [new_controller_class, code + "end # BrickGem controller\n"]
1420
1499
  when 'BrickOpenapi'
1421
1500
  is_openapi = true
@@ -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))
@@ -212,6 +214,7 @@ module Brick
212
214
  end.html_safe
213
215
  table_options << "<option value=\"#{prefix}brick_status\">(Status)</option>".html_safe if ::Brick.config.add_status
214
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
215
218
  css = +"<style>
216
219
  h1, h3 {
217
220
  margin-bottom: 0;
@@ -1093,6 +1096,16 @@ erDiagram
1093
1096
  #{script}"
1094
1097
  end
1095
1098
 
1099
+ when 'crosstab'
1100
+ if is_crosstab && ::Brick.config.license
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).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
+
1096
1109
  when 'show', 'new', 'update'
1097
1110
  +"<html>
1098
1111
  <head>
@@ -1282,7 +1295,8 @@ end}
1282
1295
  "
1283
1296
 
1284
1297
  end
1285
- inline << "
1298
+ unless is_crosstab
1299
+ inline << "
1286
1300
  <% if is_includes_dates %>
1287
1301
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css\">
1288
1302
  <style>
@@ -1416,6 +1430,7 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
1416
1430
  }
1417
1431
  });
1418
1432
  </script>"
1433
+ end
1419
1434
  # puts "==============="
1420
1435
  # puts inline
1421
1436
  # puts "==============="
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 94
8
+ TINY = 96
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
@@ -466,6 +466,10 @@ module Brick
466
466
  Brick.config.default_route_fallback = resource_name
467
467
  end
468
468
 
469
+ def license=(key)
470
+ Brick.config.license = key
471
+ end
472
+
469
473
  # Load additional references (virtual foreign keys)
470
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
471
475
  # %%% Maybe look for differences the second time 'round and just add new stuff instead of entirely deferring
@@ -691,6 +695,11 @@ In config/initializers/brick.rb appropriate entries would look something like:
691
695
  get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
692
696
  end
693
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
+
694
703
  unless ::Brick.routes_done
695
704
  if Object.const_defined?('Rswag::Ui')
696
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
@@ -1248,6 +1257,69 @@ module ActiveRecord
1248
1257
  # For AR >= 4.2
1249
1258
  if self.const_defined?('JoinDependency')
1250
1259
  class JoinDependency
1260
+ if ActiveRecord.version >= ::Gem::Version.new('6.0')
1261
+ # An intelligent .eager_load() and .includes() that creates t0_r0 style aliases only for the columns
1262
+ # used in .select(). To enable this behaviour, include the flag :_brick_eager_load as the first
1263
+ # entry in your .select().
1264
+ # More information: https://discuss.rubyonrails.org/t/includes-and-select-for-joined-data/81640
1265
+ def apply_column_aliases(relation)
1266
+ used_cols = {}
1267
+ if (sel_vals = relation.select_values.map(&:to_s)).first == '_brick_eager_load'
1268
+ # Find and expand out all column names being used in select(...)
1269
+ new_select_values = sel_vals.each_with_object([]) do |col, s|
1270
+ next if col == '_brick_eager_load'
1271
+
1272
+ if col.include?(' ') # Some expression? (No chance for a simple column reference)
1273
+ s << col # Just pass it through
1274
+ else
1275
+ col = if (col_parts = col.split('.')).length == 1
1276
+ [col]
1277
+ else
1278
+ [col_parts[0..-2].join('.'), col_parts.last]
1279
+ end
1280
+ used_cols[col] = nil
1281
+ end
1282
+ end
1283
+ if new_select_values.present?
1284
+ relation.select_values = new_select_values
1285
+ else
1286
+ relation.select_values.clear
1287
+ end
1288
+ end
1289
+
1290
+ @aliases ||= Aliases.new(join_root.each_with_index.map do |join_part, i|
1291
+ join_alias = join_part.table&.table_alias || join_part.table_name
1292
+ keys = [join_part.base_klass.primary_key] # Always include the primary key
1293
+
1294
+ # # %%% Optional to include all foreign keys:
1295
+ # keys.concat(join_part.base_klass.reflect_on_all_associations.select { |a| a.belongs_to? }.map(&:foreign_key))
1296
+
1297
+ # Add foreign keys out to referenced tables that we belongs_to
1298
+ join_part.children.each { |child| keys << child.reflection.foreign_key if child.reflection.belongs_to? }
1299
+
1300
+ # Add the foreign key that got us here -- "the train we rode in on" -- if we arrived from
1301
+ # a has_many or has_one:
1302
+ if join_part.is_a?(ActiveRecord::Associations::JoinDependency::JoinAssociation) &&
1303
+ !join_part.reflection.belongs_to?
1304
+ keys << join_part.reflection.foreign_key
1305
+ end
1306
+ keys = keys.compact # In case we're using composite_primary_keys
1307
+ j = 0
1308
+ columns = join_part.column_names.each_with_object([]) do |column_name, s|
1309
+ # Include columns chosen in select(...) as well as the PK and any relevant FKs
1310
+ if used_cols.keys.find { |c| (c.length == 1 || c.first == join_alias) && c.last == column_name } ||
1311
+ keys.find { |c| c == column_name }
1312
+ s << Aliases::Column.new(column_name, "t#{i}_r#{j}")
1313
+ end
1314
+ j += 1
1315
+ end
1316
+ Aliases::Table.new(join_part, columns)
1317
+ end)
1318
+
1319
+ relation._select!(-> { @aliases.columns })
1320
+ end
1321
+ end
1322
+
1251
1323
  private
1252
1324
 
1253
1325
  # %%% Pretty much have to flat-out replace this guy (I think anyway)
@@ -1283,7 +1355,6 @@ module ActiveRecord
1283
1355
  if (relation = node.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
1284
1356
  relation.brick_links[link_path] = result.first.table_alias || result.first.table_name
1285
1357
  end
1286
-
1287
1358
  result
1288
1359
  end
1289
1360
  else # Same idea but for Rails 7
@@ -1293,9 +1364,9 @@ module ActiveRecord
1293
1364
 
1294
1365
  # Capture the table alias name that was chosen
1295
1366
  link_path = child.instance_variable_get(:@link_path)
1296
- relation = child.instance_variable_get(:@assocs)&.instance_variable_get(:@relation)
1297
- # binding.pry if relation
1298
- relation.brick_links[link_path] = result.first.left.table_alias || result.first.left.table_name
1367
+ if (relation = child.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
1368
+ relation.brick_links[link_path] = result.first.left.table_alias || result.first.left.table_name
1369
+ end
1299
1370
  result
1300
1371
  end
1301
1372
  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.94
4
+ version: 1.0.96
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-22 00:00:00.000000000 Z
11
+ date: 2022-11-25 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