brick 1.0.37 → 1.0.40

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: '0178c8135edf190cf65ae2a4b274bde10cc5410f33adab7337798d98636f810f'
4
- data.tar.gz: c4c51e9e22548f9f49a08a7550eb003c0075b65bac70fffea82b919b7aa07131
3
+ metadata.gz: cf29d4af64b65c01ae68df45762b816403a1ebc0035e1221552f249ff3ba2ce9
4
+ data.tar.gz: 076cdb672d7c2137cc74421467d72491e9ea89751fab32015f427158fc367b45
5
5
  SHA512:
6
- metadata.gz: b6ad8492e3b19f526af3cc69f53f30e3a4de0f172f73362d36f6dbac884f7137bb7a3944f99054f61279c2fdc27989e9b840e68cb48c23aa378f6ac4bd4d2cfb
7
- data.tar.gz: def025f413aa8c89f8f603f4e3422427f9d71b10b7c57c0db6d5fb750b0e26352dc88f3d85f01b526127d54a789e6b056dc7acee64e9046909a039e576421587
6
+ metadata.gz: ce17038567ddd9d6d9d9dce6eb71fd50612d167b56351898ef9b3f9441d1a7ef3ee4ec3a57f3f24e613829e6c8a2f59517fc0d4e9266f11166b92821310b0c06
7
+ data.tar.gz: 6a7bb38b009853594a8bf1ab28489bbc92d6300fcd0f8497c7950e81c6a62a4c6da2bd65840f93bd7afea40153727cb93914529120ecf9b0dceca39b21fef880
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_record/version'
4
-
5
4
  # ActiveRecord before 4.0 didn't have #version
6
5
  unless ActiveRecord.respond_to?(:version)
7
6
  module ActiveRecord
@@ -11,6 +10,16 @@ unless ActiveRecord.respond_to?(:version)
11
10
  end
12
11
  end
13
12
 
13
+ require 'action_view'
14
+ # Older ActionView didn't have #version
15
+ unless ActionView.respond_to?(:version)
16
+ module ActionView
17
+ def self.version
18
+ ActionPack.version
19
+ end
20
+ end
21
+ end
22
+
14
23
  # In ActiveSupport older than 5.0, the duplicable? test tries to new up a BigDecimal,
15
24
  # and Ruby 2.6 and later deprecates #new. This removes the warning from BigDecimal.
16
25
  # This compatibility needs to be put into place in the application's "config/boot.rb"
@@ -70,7 +70,11 @@ module ActiveRecord
70
70
  def self._brick_primary_key(relation = nil)
71
71
  return instance_variable_get(:@_brick_primary_key) if instance_variable_defined?(:@_brick_primary_key)
72
72
 
73
- pk = primary_key.is_a?(String) ? [primary_key] : primary_key || []
73
+ pk = begin
74
+ primary_key.is_a?(String) ? [primary_key] : primary_key || []
75
+ rescue
76
+ []
77
+ end
74
78
  # Just return [] if we're missing any part of the primary key. (PK is usually just "id")
75
79
  if relation && pk.present?
76
80
  @_brick_primary_key ||= pk.any? { |pk_part| !relation[:cols].key?(pk_part) } ? [] : pk
@@ -204,7 +208,7 @@ module ActiveRecord
204
208
  assoc_name = CGI.escapeHTML(assoc_name.to_s)
205
209
  model_path = Rails.application.routes.url_helpers.send("#{model_underscore.tr('/', '_').pluralize}_path".to_sym)
206
210
  av_class = Class.new.extend(ActionView::Helpers::UrlHelper)
207
- av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('6.1')
211
+ av_class.extend(ActionView::Helpers::TagHelper) if ActionView.version < ::Gem::Version.new('7')
208
212
  link = av_class.link_to(name, model_path)
209
213
  model_underscore == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
210
214
  end
@@ -552,7 +556,7 @@ Module.class_exec do
552
556
  # return my_const
553
557
  end
554
558
 
555
- relations = ::Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
559
+ relations = ::Brick.relations
556
560
  # puts "ON OBJECT: #{args.inspect}" if self.module_parent == Object
557
561
  result = if ::Brick.enable_controllers? && class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
558
562
  # Otherwise now it's up to us to fill in the gaps
@@ -627,8 +631,8 @@ Module.class_exec do
627
631
  # module_prefixes.unshift('') unless module_prefixes.first.blank?
628
632
  # candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb')
629
633
  self._brick_const_missing(*args)
630
- elsif self != Object
631
- module_parent.const_missing(*args)
634
+ # elsif self != Object
635
+ # module_parent.const_missing(*args)
632
636
  else
633
637
  puts "MISSING! #{self.name} #{args.inspect} #{table_name}"
634
638
  self._brick_const_missing(*args)
@@ -1188,8 +1192,13 @@ module ActiveRecord::ConnectionHandling
1188
1192
  case ActiveRecord::Base.connection.adapter_name
1189
1193
  when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
1190
1194
  # schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1195
+ ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
1196
+ ActiveRecord::Base.schema_migrations_table_name
1197
+ else
1198
+ 'schema_migrations'
1199
+ end
1191
1200
  ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
1192
- ActiveRecord::Base.execute_sql(sql, ActiveRecord::Base.schema_migrations_table_name, ar_imtn).each do |r|
1201
+ ActiveRecord::Base.execute_sql(sql, ar_smtn, ar_imtn).each do |r|
1193
1202
  # If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
1194
1203
  # is the default schema, usually 'public'.
1195
1204
  schema_name = if ::Brick.config.schema_behavior[:multitenant]
@@ -50,23 +50,26 @@ module Brick
50
50
  # ====================================
51
51
  if ::Brick.enable_views?
52
52
  ActionView::LookupContext.class_exec do
53
+ # Used by Rails 5.0 and above
53
54
  alias :_brick_template_exists? :template_exists?
54
55
  def template_exists?(*args, **options)
55
- unless (is_template_exists = _brick_template_exists?(*args, **options))
56
- # Need to return true if we can fill in the blanks for a missing one
57
- # args will be something like: ["index", ["categories"]]
58
- args[1] = args[1].each_with_object([]) { |a, s| s.concat(a.split('/')) }
59
- args[1][args[1].length - 1] = args[1].last.singularize # Make sure the last item, defining the class name, is singular
60
- model = args[1].map(&:camelize).join('::').constantize
61
- if is_template_exists = model && (
62
- ['index', 'show'].include?(args.first) || # Everything has index and show
56
+ _brick_template_exists?(*args, **options) || set_brick_model(args)
57
+ end
58
+
59
+ def set_brick_model(find_args)
60
+ # Need to return true if we can fill in the blanks for a missing one
61
+ # args will be something like: ["index", ["categories"]]
62
+ find_args[1] = find_args[1].each_with_object([]) { |a, s| s.concat(a.split('/')) }
63
+ if (class_name = find_args[1].last&.singularize)
64
+ find_args[1][find_args[1].length - 1] = class_name # Make sure the last item, defining the class name, is singular
65
+ if (model = find_args[1].map(&:camelize).join('::').constantize) && (
66
+ ['index', 'show'].include?(find_args.first) || # Everything has index and show
63
67
  # Only CUD stuff has create / update / destroy
64
- (!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
68
+ (!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(find_args.first))
65
69
  )
66
70
  @_brick_model = model
67
71
  end
68
72
  end
69
- is_template_exists
70
73
  end
71
74
 
72
75
  def path_keys(hm_assoc, fk_name, obj_name, pk)
@@ -82,9 +85,13 @@ module Brick
82
85
 
83
86
  alias :_brick_find_template :find_template
84
87
  def find_template(*args, **options)
85
- return _brick_find_template(*args, **options) unless @_brick_model
88
+ unless (model_name = (
89
+ @_brick_model ||
90
+ (ActionView.version < ::Gem::Version.new('5.0') && args[1].is_a?(Array) ? set_brick_model(args) : nil)
91
+ )&.name)
92
+ return _brick_find_template(*args, **options)
93
+ end
86
94
 
87
- model_name = @_brick_model.name
88
95
  pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(model_name, nil))
89
96
  obj_name = model_name.split('::').last.underscore
90
97
  path_obj_name = model_name.underscore.tr('/', '_')
@@ -462,8 +469,8 @@ if (headerTop) {
462
469
  <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
463
470
  <select id=\"tbl\">#{table_options}</select>
464
471
  <h1>#{model_plural = model_name.pluralize}</h1>#{template_link}<%
465
- if (relation = Brick.relations[#{model_name}.table_name])[:description] %><%=
466
- relation.fetch(:description, nil) %><br><%
472
+ if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
473
+ description %><br><%
467
474
  end
468
475
  if @_brick_params&.present? %>
469
476
  <% if @_brick_params.length == 1 # %%% Does not yet work with composite keys
@@ -488,7 +495,7 @@ if (headerTop) {
488
495
  ::Brick.config.metadata_columns.include?(col_name) || poly_cols.include?(col_name)
489
496
 
490
497
  col_order << col_name
491
- %><th<%= \" title = \\\"#\{col.comment}\\\"\".html_safe unless col.comment.blank? %>><%
498
+ %><th<%= \" title = \\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %>><%
492
499
  if (bt = bts[col_name]) %>
493
500
  BT <%
494
501
  bt[1].each do |bt_pair| %><%=
@@ -522,6 +529,7 @@ if (headerTop) {
522
529
  # 0..62 because Postgres column names are limited to 63 characters
523
530
  #{obj_name}, (descrips = @_brick_bt_descrip[bt.first][bt_class])[0..-2].map { |z| #{obj_name}.send(z.last[0..62]) }, (bt_id_col = descrips.last)
524
531
  )
532
+ bt_txt ||= \"<< Orphaned ID: #\{val} >>\" if val
525
533
  bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
526
534
  <%= bt_id ? link_to(bt_txt, send(\"#\{bt_class.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, bt_id)) : bt_txt %>
527
535
  <%#= Previously was: bt_obj = bt[1].first.first.find_by(bt[2] => val); link_to(bt_obj.brick_descrip, send(\"#\{bt[1].first.first.name.underscore\}_path\".to_sym, bt_obj.send(bt[1].first.first.primary_key.to_sym))) if bt_obj %>
@@ -545,8 +553,8 @@ if (headerTop) {
545
553
  <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
546
554
  <select id=\"tbl\">#{table_options}</select>
547
555
  <h1>#{model_name}: <%= (obj = @#{obj_name})&.brick_descrip || controller_name %></h1><%
548
- if (relation = Brick.relations[#{model_name}.table_name])[:description] %><%=
549
- relation.fetch(:description, nil) %><br><%
556
+ if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
557
+ description %><br><%
550
558
  end
551
559
  %><%= link_to '(See all #{obj_name.pluralize})', #{path_obj_name.pluralize}_path %>
552
560
  <% if obj %>
@@ -562,7 +570,7 @@ end
562
570
  <tr>
563
571
  <% next if (#{(pk || []).inspect}.include?(k) && !bts.key?(k)) ||
564
572
  ::Brick.config.metadata_columns.include?(k) %>
565
- <th class=\"show-field\"<%= \" title = \\\"#\{col.comment}\\\"\".html_safe unless col.comment.blank? %>>
573
+ <th class=\"show-field\"<%= \" title = \\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %>>
566
574
  <% has_fields = true
567
575
  if (bt = bts[k])
568
576
  # Add a final member in this array with descriptive options to be used in <select> drop-downs
@@ -603,7 +611,11 @@ end
603
611
  html_options = { prompt: \"Select #\{bt_name\}\" }
604
612
  html_options[:class] = 'dimmed' unless val %>
605
613
  <%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
606
- <%= bt_obj = bt_class&.find_by(bt_pair[1] => val); link_to('⇛', send(\"#\{bt_class.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' }) if bt_obj %>
614
+ <%= if (bt_obj = bt_class&.find_by(bt_pair[1] => val))
615
+ link_to('⇛', send(\"#\{bt_class.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, bt_obj.send(bt_class.primary_key.to_sym)), { class: 'show-arrow' })
616
+ elsif val
617
+ \"Orphaned ID: #\{val}\"
618
+ end %>
607
619
  <% else case #{model_name}.column_for_attribute(k).type
608
620
  when :string, :text %>
609
621
  <% if is_bcrypt?(val) # || .readonly? %>
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ if Object.const_defined?('::Rake::TaskManager')
4
+ namespace :brick do
5
+ desc 'Find any seemingly-orphaned records'
6
+ task orphans: :environment do
7
+ def class_pk(dotted_name, multitenant)
8
+ Object.const_get((multitenant ? [dotted_name.split('.').last] : dotted_name.split('.')).map { |nm| "::#{nm.singularize.camelize}" }.join).primary_key
9
+ end
10
+
11
+ schema_list = ((multi = ::Brick.config.schema_behavior[:multitenant]) && ::Brick.db_schemas.keys.sort) || []
12
+ if schema_list.length > 1
13
+ require 'fancy_gets'
14
+ include FancyGets
15
+ schema = gets_list(
16
+ list: schema_list,
17
+ chosen: multi[:schema_to_analyse]
18
+ )
19
+ elsif schema_list.length.positive?
20
+ schema = schema_list.first
21
+ end
22
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema) if schema
23
+ orphans = +''
24
+ ::Brick.relations.each do |k, v|
25
+ next if v.key?(:isView) || ::Brick.config.exclude_tables.include?(k) ||
26
+ !(pri_pk = v[:pkey].values.first&.first) ||
27
+ !(pri_pk = class_pk(k, multi))
28
+ v[:fks].each do |k1, v1|
29
+ next if v1[:is_bt] ||
30
+ !(for_rel = ::Brick.relations.fetch(v1[:inverse_table], nil)) ||
31
+ v1[:inverse]&.key?(:polymorphic) ||
32
+ !(for_pk = for_rel.fetch(:pkey, nil)&.values&.first&.first) ||
33
+ !(for_pk = class_pk(v1[:inverse_table], multi))
34
+ begin
35
+ ActiveRecord::Base.execute_sql(
36
+ "SELECT DISTINCT frn.#{v1[:fk]} AS pri_id, frn.#{for_pk} AS fk_id
37
+ FROM #{v1[:inverse_table]} AS frn
38
+ LEFT OUTER JOIN #{k} AS pri ON pri.#{pri_pk} = frn.#{v1[:fk]}
39
+ WHERE frn.#{v1[:fk]} IS NOT NULL AND pri.#{pri_pk} IS NULL
40
+ ORDER BY 1, 2"
41
+ ).each do |o|
42
+ orphans << "#{v1[:inverse_table]} #{o['fk_id']} refers to non-existant #{k} #{o['pri_id']}\n"
43
+ end
44
+ rescue StandardError => err
45
+ puts "Strange -- #{err.inspect}"
46
+ end
47
+ end
48
+ end
49
+ puts "For #{schema}:\n#{'=' * (schema.length + 5)}" if schema
50
+ if orphans.blank?
51
+ puts "No orphans!"
52
+ else
53
+ print orphans
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/brick/util.rb CHANGED
@@ -5,7 +5,7 @@ module Brick
5
5
  module Util
6
6
  # ===================================
7
7
  # Epic require patch
8
- def self._patch_require(module_filename, folder_matcher, search_text, replacement_text, autoload_symbol = nil, is_bundler = false)
8
+ def self._patch_require(module_filename, folder_matcher, replacements, autoload_symbol = nil, is_bundler = false)
9
9
  mod_name_parts = module_filename.split('.')
10
10
  extension = case mod_name_parts.last
11
11
  when 'rb', 'so', 'o'
@@ -28,10 +28,13 @@ module Brick
28
28
  Dir.mkdir(new_part) unless Dir.exist?(new_part)
29
29
  new_part
30
30
  end
31
- if ::Brick::Util._write_patched(folder_matcher, module_filename, extension, custom_require_dir, nil, search_text, replacement_text) &&
31
+ if ::Brick::Util._write_patched(folder_matcher, module_filename, extension, custom_require_dir, nil, replacements) &&
32
32
  !alp.include?(custom_require_dir)
33
33
  alp.unshift(custom_require_dir)
34
34
  end
35
+ # require 'pry-byebug'
36
+ # binding.pry
37
+ # z = 10
35
38
  elsif is_bundler
36
39
  puts "Bundler hack"
37
40
  require 'pry-byebug'
@@ -56,13 +59,13 @@ module Brick
56
59
  define_method(:require) do |name|
57
60
  puts name if name.to_s.include?('cucu')
58
61
  if (require_override = ::Brick::Util.instance_variable_get(:@_require_overrides)[name])
59
- extension, folder_matcher, search_text, replacement_text, autoload_symbol = require_override
62
+ extension, folder_matcher, replacements, autoload_symbol = require_override
60
63
  patched_filename = "/patched_#{name.tr('/', '_')}#{extension}"
61
64
  if $LOADED_FEATURES.find { |f| f.end_with?(patched_filename) }
62
65
  false
63
66
  else
64
67
  is_replaced = false
65
- if (replacement_path = ::Brick::Util._write_patched(folder_matcher, name, extension, ::Brick::Util._custom_require_dir, patched_filename, search_text, replacement_text))
68
+ if (replacement_path = ::Brick::Util._write_patched(folder_matcher, name, extension, ::Brick::Util._custom_require_dir, patched_filename, replacements))
66
69
  is_replaced = Kernel.send(:orig_require, replacement_path)
67
70
  elsif replacement_path.nil?
68
71
  puts "Couldn't find #{name} to require it!"
@@ -75,7 +78,7 @@ module Brick
75
78
  end
76
79
  end
77
80
  end
78
- require_overrides[module_filename] = [extension, folder_matcher, search_text, replacement_text, autoload_symbol]
81
+ require_overrides[module_filename] = [extension, folder_matcher, replacements, autoload_symbol]
79
82
  end
80
83
  end
81
84
 
@@ -94,7 +97,7 @@ module Brick
94
97
 
95
98
  # Returns the full path to the replaced filename, or
96
99
  # false if the file already exists, and nil if it was unable to write anything.
97
- def self._write_patched(folder_matcher, name, extension, dir, patched_filename, search_text, replacement_text)
100
+ def self._write_patched(folder_matcher, name, extension, dir, patched_filename, replacements)
98
101
  # See if our replacement file might already exist for some reason
99
102
  name = +"/#{name}" unless name.start_with?('/')
100
103
  name << extension unless name.end_with?(extension)
@@ -111,9 +114,13 @@ module Brick
111
114
  break if path.include?(folder_matcher) && (orig_as = File.open(orig_path))
112
115
  end
113
116
  puts [folder_matcher, name].inspect
114
- if (orig_text = orig_as&.read)
115
- File.open(replacement_path, 'w') do |replacement|
116
- num_written = replacement.write(orig_text.gsub(search_text, replacement_text))
117
+ if (updated_text = orig_as&.read)
118
+ File.open(replacement_path, 'w') do |replaced_file|
119
+ replacements = [replacements] unless replacements.first.is_a?(Array)
120
+ replacements.each do |search_text, replacement_text|
121
+ updated_text.gsub!(search_text, replacement_text)
122
+ end
123
+ num_written = replaced_file.write(updated_text)
117
124
  end
118
125
  orig_as.close
119
126
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 37
8
+ TINY = 40
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
@@ -2,10 +2,10 @@
2
2
 
3
3
  require 'brick/compatibility'
4
4
 
5
- # Allow ActiveRecord 4.0 and 4.1 to work with newer Ruby (>= 2.4) by avoiding a "stack level too deep"
5
+ # Allow ActiveRecord 4.2.7 and older to work with newer Ruby (>= 2.4) by avoiding a "stack level too deep"
6
6
  # error when ActiveSupport tries to smarten up Numeric by messing with Fixnum and Bignum at the end of:
7
7
  # activesupport-4.0.13/lib/active_support/core_ext/numeric/conversions.rb
8
- if ActiveRecord.version < ::Gem::Version.new('4.2') &&
8
+ if ActiveRecord.version < ::Gem::Version.new('4.2.8') &&
9
9
  ActiveRecord.version > ::Gem::Version.new('3.2') &&
10
10
  Object.const_defined?('Integer') && Integer.superclass.name == 'Numeric'
11
11
  class OurFixnum < Integer; end
@@ -31,25 +31,78 @@ if (ruby_version = ::Gem::Version.new(RUBY_VERSION)) >= ::Gem::Version.new('2.7'
31
31
  # Remove circular reference for "now"
32
32
  ::Brick::Util._patch_require(
33
33
  'active_support/values/time_zone.rb', '/activesupport',
34
- ' def parse(str, now=now)',
35
- ' def parse(str, now=now())'
34
+ [' def parse(str, now=now)',
35
+ ' def parse(str, now=now())']
36
36
  )
37
37
  # Remove circular reference for "reflection" for ActiveRecord 3.1
38
38
  if ActiveRecord.version >= ::Gem::Version.new('3.1')
39
39
  ::Brick::Util._patch_require(
40
40
  'active_record/associations/has_many_association.rb', '/activerecord',
41
- 'reflection = reflection)',
42
- 'reflection = reflection())',
41
+ ['reflection = reflection)',
42
+ 'reflection = reflection())'],
43
43
  :HasManyAssociation # Make sure the path for this guy is available to be autoloaded
44
44
  )
45
45
  end
46
46
  end
47
47
 
48
+ # Add left_outer_join! to Associations::JoinDependency and Relation::QueryMethods
49
+ if ActiveRecord.version < ::Gem::Version.new('5')
50
+ is_add_left_outer_join = true
51
+ ::Brick::Util._patch_require(
52
+ 'active_record/associations/join_dependency.rb', '/activerecord', # /associations
53
+ ["def join_constraints(outer_joins)
54
+ joins = join_root.children.flat_map { |child|
55
+ make_inner_joins join_root, child
56
+ }",
57
+ "def join_constraints(outer_joins, join_type)
58
+ joins = join_root.children.flat_map { |child|
59
+
60
+ if join_type == Arel::Nodes::OuterJoin
61
+ make_left_outer_joins join_root, child
62
+ else
63
+ make_inner_joins join_root, child
64
+ end
65
+ }"],
66
+ :JoinDependency # This one is in an "eager_autoload do" -- so how to handle it?
67
+ )
68
+
69
+ # Three changes all in the same file, query_methods.rb:
70
+ ::Brick::Util._patch_require(
71
+ 'active_record/relation/query_methods.rb', '/activerecord',
72
+ [
73
+ # Change 1 - Line 904
74
+ ['build_joins(arel, joins_values.flatten) unless joins_values.empty?',
75
+ "build_joins(arel, joins_values.flatten) unless joins_values.empty?
76
+ build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty?"
77
+ ],
78
+ # Change 2 - Line 992
79
+ ["raise 'unknown class: %s' % join.class.name
80
+ end
81
+ end",
82
+ "raise 'unknown class: %s' % join.class.name
83
+ end
84
+ end
85
+
86
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin)
87
+ end
88
+
89
+ def build_join_query(manager, buckets, join_type)"
90
+ ],
91
+ # Change 3 - Line 1012
92
+ ['join_infos = join_dependency.join_constraints stashed_association_joins',
93
+ 'join_infos = join_dependency.join_constraints stashed_association_joins, join_type'
94
+ ]
95
+ ],
96
+ :QueryMethods
97
+ )
98
+ end
99
+
100
+
48
101
  # puts ::Brick::Util._patch_require(
49
102
  # 'cucumber/cli/options.rb', '/cucumber/cli/options', # /cli/options
50
- # ' def extract_environment_variables',
51
- # " def extract_environment_variables\n
52
- # puts 'Patch test!'"
103
+ # [' def extract_environment_variables',
104
+ # " def extract_environment_variables\n
105
+ # puts 'Patch test!'"]
53
106
  # ).inspect
54
107
 
55
108
  # An ActiveRecord extension that uses INFORMATION_SCHEMA views to reflect on all
@@ -295,10 +348,11 @@ module Brick
295
348
 
296
349
  relations = ::Brick.relations
297
350
  if (ars = ::Brick.config.additional_references) || ::Brick.config.polymorphics
351
+ is_optional = ActiveRecord.version >= ::Gem::Version.new('5.0')
298
352
  if ars
299
353
  ars.each do |ar|
300
354
  fk = ar.length < 5 ? [nil, +ar[0], ar[1], nil, +ar[2]] : [ar[0], +ar[1], ar[2], ar[3], +ar[4], ar[5]]
301
- ::Brick._add_bt_and_hm(fk, relations, false, true)
355
+ ::Brick._add_bt_and_hm(fk, relations, false, is_optional)
302
356
  end
303
357
  end
304
358
  if (polys = ::Brick.config.polymorphics)
@@ -311,7 +365,7 @@ module Brick
311
365
  v ||= ActiveRecord::Base.execute_sql("SELECT DISTINCT #{poly}_type AS typ FROM #{table_name}").each_with_object([]) { |result, s| s << result['typ'] if result['typ'] }
312
366
  v.each do |type|
313
367
  if relations.key?(primary_table = type.underscore.pluralize)
314
- ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true, true)
368
+ ::Brick._add_bt_and_hm([nil, table_name, poly, nil, primary_table, "(brick) #{table_name}_#{poly}"], relations, true, is_optional)
315
369
  else
316
370
  missing_stis[primary_table] = type unless ::Brick.existing_stis.key?(type)
317
371
  end
@@ -414,7 +468,24 @@ end
414
468
 
415
469
  require 'brick/version_number'
416
470
 
471
+ # Older versions of ActiveRecord would only show more serious error information from "panic" level, which is
472
+ # a level only available in Postgres 12 and older. This patch will allow older and newer versions of Postgres
473
+ # to work along with fairly old versions of Rails.
474
+ if Object.const_defined?('PG::VERSION') && ActiveRecord.version < ::Gem::Version.new('4.2.6')
475
+ ::Brick::Util._patch_require(
476
+ 'active_record/connection_adapters/postgresql_adapter.rb', '/activerecord', ["'panic'", "'error'"]
477
+ )
478
+ end
479
+
417
480
  require 'active_record'
481
+ require 'active_record/relation'
482
+ require 'active_record/relation/query_methods' if is_add_left_outer_join
483
+
484
+ # Rake tasks
485
+ class Railtie < Rails::Railtie
486
+ Dir.glob("#{File.expand_path(__dir__)}/brick/tasks/**/*.rake").each { |task| load task }
487
+ end
488
+
418
489
  # Major compatibility fixes for ActiveRecord < 4.2
419
490
  # ================================================
420
491
  ActiveSupport.on_load(:active_record) do
@@ -633,6 +704,62 @@ ActiveSupport.on_load(:active_record) do
633
704
  end
634
705
  end
635
706
  end
707
+
708
+ if is_add_left_outer_join
709
+ # Final pieces for left_outer_joins support, which was derived from this commit:
710
+ # https://github.com/rails/rails/commit/3f46ef1ddab87482b730a3f53987e04308783d8b
711
+ module Associations
712
+ class JoinDependency
713
+ def make_left_outer_joins(parent, child)
714
+ tables = child.tables
715
+ join_type = Arel::Nodes::OuterJoin
716
+ info = make_constraints parent, child, tables, join_type
717
+
718
+ [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
719
+ end
720
+ end
721
+ end
722
+ module Querying
723
+ delegate :left_outer_joins, to: :all
724
+ end
725
+ class Relation
726
+ MULTI_VALUE_METHODS = MULTI_VALUE_METHODS + [:left_outer_joins] unless MULTI_VALUE_METHODS.include?(:left_outer_joins)
727
+ end
728
+ module QueryMethods
729
+ attr_writer :left_outer_joins_values
730
+ def left_outer_joins_values
731
+ @left_outer_joins_values ||= []
732
+ end
733
+
734
+ def left_outer_joins(*args)
735
+ check_if_method_has_arguments!(:left_outer_joins, args)
736
+
737
+ args.compact!
738
+ args.flatten!
739
+
740
+ spawn.left_outer_joins!(*args)
741
+ end
742
+
743
+ def left_outer_joins!(*args) # :nodoc:
744
+ self.left_outer_joins_values += args
745
+ self
746
+ end
747
+
748
+ def build_left_outer_joins(manager, outer_joins)
749
+ buckets = outer_joins.group_by do |join|
750
+ case join
751
+ when Hash, Symbol, Array
752
+ :association_join
753
+ else
754
+ raise ArgumentError, 'only Hash, Symbol and Array are allowed'
755
+ end
756
+ end
757
+
758
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin)
759
+ end
760
+ end
761
+ end
762
+ # (End of left_outer_joins support)
636
763
  end
637
764
  end
638
765
 
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.37
4
+ version: 1.0.40
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-06-27 00:00:00.000000000 Z
11
+ date: 2022-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,20 +16,34 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.0'
19
+ version: '4.2'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '7.1'
22
+ version: '7.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '3.0'
29
+ version: '4.2'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '7.1'
32
+ version: '7.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: fancy_gets
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: appraisal
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -225,6 +239,7 @@ files:
225
239
  - lib/brick/join_array.rb
226
240
  - lib/brick/serializers/json.rb
227
241
  - lib/brick/serializers/yaml.rb
242
+ - lib/brick/tasks/orphans.rake
228
243
  - lib/brick/util.rb
229
244
  - lib/brick/version_number.rb
230
245
  - lib/generators/brick/USAGE