brick 1.0.242 → 1.0.244

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: 1e28a33c378751e44778744e81bd2841b6bdd61d06587860b2a5f9bccccbaa85
4
- data.tar.gz: 13f3272c5f8eabac2fb7c9253a1ce499fd2dd208d468b048e60c2542f3456eb9
3
+ metadata.gz: 4de3ef8371e163df0913a1fe70d55666010abefcdcc77d26813a27ea3b72967f
4
+ data.tar.gz: 73dffd4bb0f5c5e9f57d1e19550cc212a67b4cc9636dd6eed53bee458cd155ee
5
5
  SHA512:
6
- metadata.gz: b7f7c82a56a1097cd710a27f229988949e2327f9de6539302acd05a15ceea61d01b2960d98e9708fa823c24022509258ef2879b41c700822f1d8769c5734792a
7
- data.tar.gz: 679dc5f6151313ef1d7bcd9912cbe4770741c5d03a7c3a29a1d139e72f96cdf778d096fffbf2018c80b8148a91310127a184072d0a63f07905c67c157a9d3e55
6
+ metadata.gz: 948aaae186933b70b69b8df65ac2081168edd4d3badc9cf7bcdfb2d2a49243be376bb70c3c15aef7e00fdbf42f5be5c51185a91e01e4728332e0e0a476382403
7
+ data.tar.gz: 500f395af03a898ed11ee8e3caad5fa0571568b19f1183a4f7b90793a8324e5d87870220b8a60b7e91d61e73ad7855d450a46711e8404c519bded7599b761866
@@ -146,6 +146,31 @@ module ActiveRecord
146
146
  self.primary_key.is_a?(Array) ? self.primary_key : [self.primary_key]
147
147
  end
148
148
 
149
+ def _brick_deserialized_value(obj, descrip_cols)
150
+ descrip_cols.map do |descrip_part|
151
+ # 0..62 because Postgres column names are limited to 63 characters
152
+ if (val = obj.send(descrip_part[-1][0..62])) &&
153
+ ActiveRecord::ConnectionAdapters::Column.instance_methods.include?(:cast_type)
154
+ cast_type = self.columns_hash[descrip_part[-2]]&.send(:cast_type)
155
+ val = cast_type.deserialize(val)
156
+ val = val.force_encoding('UTF-8') if val.is_a?(String)
157
+ end
158
+ val
159
+ end
160
+ end
161
+
162
+ def _brick_deserialized_id(obj, id_col)
163
+ id = []
164
+ self._pk_as_array.each_with_index do |pk_part, idx|
165
+ id << if obj.respond_to?(id_sym = id_col[idx].to_sym)
166
+ self._brick_deserialized_value(obj, [[pk_part, id_col[idx]]])&.first
167
+ else
168
+ id_col[idx]
169
+ end
170
+ end
171
+ id
172
+ end
173
+
149
174
  def _br_quoted_name(name)
150
175
  name = name.join('.') if name.is_a?(Array)
151
176
  if name == '*'
@@ -189,17 +214,22 @@ module ActiveRecord
189
214
 
190
215
  # Used to show a little prettier name for an object
191
216
  def brick_get_dsl
192
- # If there's no DSL yet specified, just try to find the first usable column on this model
193
- unless (dsl = ::Brick.config.model_descrips[name])
194
- skip_columns = _brick_get_fks + (::Brick.config.metadata_columns || []) + [primary_key]
195
- dsl = if table_exists? && (descrip_col = columns.find { |c| [:boolean, :binary, :xml].exclude?(c.type) && skip_columns.exclude?(c.name) })
196
- "[#{descrip_col.name}]"
197
- else
198
- "#{name} ##{_pk_as_array.map { |pk_part| "[#{pk_part}]" }.join(', ')}"
217
+ if ::Brick.config.model_descrips.key?(name)
218
+ ::Brick.config.model_descrips[name]
219
+ else
220
+ # If there's no DSL yet specified, just try to find the first usable column on this model
221
+ dsl = if table_exists?
222
+ skip_columns = _brick_get_fks + (::Brick.config.metadata_columns || []) + [primary_key]
223
+ if (descrip_col = columns.find { |c| c.type == :string && skip_columns.exclude?(c.name) })
224
+ "[#{descrip_col.name}]"
225
+ elsif (descrip_col = columns.find { |c| [:boolean, :binary, :xml].exclude?(c.type) && skip_columns.exclude?(c.name) })
226
+ "[#{descrip_col.name}]"
227
+ else
228
+ "#{name} ##{_pk_as_array.map { |pk_part| "[#{pk_part}]" }.join(', ')}"
229
+ end
199
230
  end
200
231
  ::Brick.config.model_descrips[name] = dsl
201
232
  end
202
- dsl
203
233
  end
204
234
 
205
235
  def _brick_monetized_attributes
@@ -1447,12 +1477,12 @@ end
1447
1477
  ) ||
1448
1478
 
1449
1479
  # Try to require the respective Ruby file
1450
- # ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore)) &&
1451
- # (require_dependency(filename) || true) &&
1452
1480
  (!anonymous? && # Don't try to find classes first mentioned in .erb or .haml templates
1453
1481
  (filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
1454
1482
  (self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
1455
- ) && (require_dependency(filename) || true) &&
1483
+ ) &&
1484
+ # Replicate the functionality of ActiveSupport::Depenencies#require_dependency to support Rails >= v9.0
1485
+ (((abspath = ActiveSupport::Dependencies.search_for_file(filename)) ? (require abspath) : (require filename)) || true) &&
1456
1486
  (filename != Module.instance_variable_get(:@_brick_last_filename) || # Avoid trying the same exact file twice in a row
1457
1487
  Module.instance_variable_set(:@_brick_last_filename, nil)) &&
1458
1488
  Module.instance_variable_set(:@_brick_last_filename, filename) &&
@@ -1833,7 +1863,7 @@ class Object
1833
1863
  end
1834
1864
 
1835
1865
  # Enable Elasticsearch based on the table name?
1836
- if (@_brick_es_crud = ::Brick.elasticsearch_models&.fetch(matching, nil))
1866
+ if (@_brick_es_crud = ::Brick.elasticsearch_models.is_a?(Hash) && ::Brick.elasticsearch_models&.fetch(matching, nil))
1837
1867
  include ::Elasticsearch::Model
1838
1868
  if @_brick_es_crud.index('i') # Enable auto-creation of indexes on import?
1839
1869
  class << self
@@ -2159,34 +2189,11 @@ class Object
2159
2189
  built_controller = Class.new(controller_base) do |new_controller_class|
2160
2190
  (namespace || ::Object).const_set(class_name.to_sym, new_controller_class)
2161
2191
 
2162
- # Add a hash for the inline style to the content-security-policy if one is present
2163
- self.define_method(:add_csp_hash) do |style_value = nil|
2164
- if request.respond_to?(:content_security_policy) && (csp = request.content_security_policy)
2165
- if (cspd = csp.directives.fetch('style-src'))
2166
- if style_value
2167
- if (nonce = ::ActionDispatch::ContentSecurityPolicy::Request::NONCE)
2168
- request.env[nonce] = '' # Generally 'action_dispatch.content_security_policy_nonce'
2169
- end
2170
- # Keep only self, if present, and also add this value
2171
- cspd.select! { |val| val == "'self'" }
2172
- cspd << style_value
2173
- else
2174
- cspd << "'sha256-#{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::BRICK_CSS)).chomp}'"
2175
- end
2176
- cspd << 'https://cdn.jsdelivr.net'
2177
- end
2178
- if (cspd = csp.directives.fetch('script-src'))
2179
- cspd << 'https://cdn.jsdelivr.net'
2180
- end
2181
- end
2182
- end
2183
-
2184
2192
  # Brick-specific pages
2185
2193
  case plural_class_name
2186
2194
  when 'BrickGem'
2187
2195
  self.define_method :status do
2188
2196
  instance_variable_set(:@resources, ::Brick.get_status_of_resources)
2189
- add_csp_hash
2190
2197
  end
2191
2198
  # # if ::Brick.config.add_schema
2192
2199
  # # Currently can only do adding columns
@@ -2197,7 +2204,6 @@ class Object
2197
2204
  # base_class.reset_column_information
2198
2205
  # ::Brick.relations[base_class.table_name]&.fetch(:cols, nil)&.[]=(col_name, [col_type, nil, false, false])
2199
2206
  # # instance_variable_set(:@schema, ::Brick.find_schema(::Brick.set_db_schema(params).first))
2200
- # add_csp_hash
2201
2207
  # end
2202
2208
  # end
2203
2209
 
@@ -2206,20 +2212,17 @@ class Object
2206
2212
  if (base_class = (model = params['modelName']&.constantize).base_class)
2207
2213
  args = params['args']
2208
2214
  record = base_class.create(args[0] => args[1], args[2] => args[3])
2209
- add_csp_hash
2210
2215
  render json: { data: record.id }
2211
2216
  end
2212
2217
  end
2213
2218
  self.define_method :unassociate do
2214
2219
  if (base_class = (model = params['modelName']&.constantize).base_class)
2215
2220
  base_class.find_by(base_class._pk_as_array&.first => params['id']).delete
2216
- add_csp_hash
2217
2221
  end
2218
2222
  end
2219
2223
 
2220
2224
  self.define_method :orphans do
2221
2225
  instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params).first))
2222
- add_csp_hash
2223
2226
  end
2224
2227
  self.define_method :crosstab do
2225
2228
  @relations = ::Brick.relations.each_with_object({}) do |r, s|
@@ -2460,7 +2463,16 @@ class Object
2460
2463
  if request.format == :csv # Asking for a template?
2461
2464
  require 'csv'
2462
2465
  exported_csv = CSV.generate(force_quotes: false) do |csv_out|
2463
- real_model.df_export(real_model.brick_import_template).each { |row| csv_out << row }
2466
+ real_model.df_export(true, real_model.brick_import_template, false, wheres, order_by).each do |row|
2467
+ row.each do |d|
2468
+ row.each_with_index do |d, idx|
2469
+ # "false" disallows HTML encoding the descriptions of binary content
2470
+ # Max length for text is only 10,000 characters
2471
+ row[idx] = ::Brick::Rails.hide_bcrypt(d, false, 10_000) if d.encoding != CSV::ConverterEncoding
2472
+ end
2473
+ end
2474
+ csv_out << row
2475
+ end
2464
2476
  end
2465
2477
  render inline: exported_csv, content_type: request.format
2466
2478
  return
@@ -2551,7 +2563,6 @@ class Object
2551
2563
  @_brick_hm_counts = real_model._br_hm_counts
2552
2564
  @_brick_join_array = join_array
2553
2565
  @_brick_erd = params['_brick_erd']&.to_i
2554
- add_csp_hash
2555
2566
  end
2556
2567
 
2557
2568
  _, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk, true)) if pk
@@ -2593,7 +2604,6 @@ class Object
2593
2604
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
2594
2605
  _, real_singular_table_name = model.real_singular(params)
2595
2606
  instance_variable_set("@#{real_singular_table_name}".to_sym, send(find_obj))
2596
- add_csp_hash("'unsafe-inline'")
2597
2607
  end
2598
2608
  end
2599
2609
 
@@ -2629,7 +2639,6 @@ class Object
2629
2639
  end if Object.const_defined?('ActiveStorage')
2630
2640
  end
2631
2641
  instance_variable_set("@#{real_singular_table_name}".to_sym, new_obj)
2632
- add_csp_hash
2633
2642
  end
2634
2643
 
2635
2644
  code << " def create\n"
@@ -2705,7 +2714,6 @@ class Object
2705
2714
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
2706
2715
  _, real_singular_table_name = model.real_singular(params)
2707
2716
  instance_variable_set("@#{real_singular_table_name}".to_sym, send(find_obj))
2708
- add_csp_hash
2709
2717
  end
2710
2718
 
2711
2719
  code << " def update\n"
Binary file
@@ -640,7 +640,14 @@ window.addEventListener(\"popstate\", linkSchemas);
640
640
  keys = if hm_assoc.macro == :has_and_belongs_to_many
641
641
  # %%% Can a HABTM use composite keys?
642
642
  # (If so then this should be rewritten to do a .zip() )
643
- name_from_other_direction = hm_assoc.klass.reflect_on_all_associations.find { |a| a.join_table == hm_assoc.join_table }&.name
643
+ name_from_other_direction = hm_assoc.klass.reflect_on_all_associations.find do |a|
644
+ begin
645
+ a.join_table == hm_assoc.join_table
646
+ rescue NameError => ee
647
+ # Can be something like: Missing model class AssignedCard for the User#assigned_cards association. You can specify a different model class with the :class_name option.
648
+ # (Happens when referencing the Fizzy Filter model.)
649
+ end
650
+ end&.name
644
651
  [["#{name_from_other_direction}.#{pk.first}", pk.first]]
645
652
  else
646
653
  if fk_name.is_a?(Array) && pk.is_a?(Array) # Composite keys?
@@ -819,15 +826,56 @@ window.addEventListener(\"popstate\", linkSchemas);
819
826
  ['Crosstab', is_crosstab]].each do |table_option, show_it|
820
827
  table_options << "<option value=\"#{prefix}brick_#{table_option.downcase}\">(#{table_option})</option>".html_safe if show_it
821
828
  end
822
- css = +'<style>'
823
- css << ::Brick::Rails::BRICK_CSS
824
- css << "</style>
829
+ css = +"<style>#{::Brick::Rails::BRICK_CSS}</style>
825
830
  <script>
826
831
  if (window.history.state && window.history.state.turbo)
827
832
  window.addEventListener(\"popstate\", function () { location.reload(true); });
828
833
  </script>
829
834
 
830
835
  <%
836
+ if @_request.respond_to?(:content_security_policy)
837
+ @_request.env['_is_brick'] = true
838
+ if (csp = @_request.content_security_policy).instance_variables.exclude?(:@_brick_style_shas)
839
+ csp.instance_variable_set(:@_brick_style_shas, [
840
+ \"'sha256-#\{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::BRICK_CSS)).chomp}'\",
841
+ \"'sha256-#\{Base64.encode64(Digest.const_get(:SHA256).digest(::Brick::Rails::IN_APP_STYLE)).chomp}'\"
842
+ ])
843
+ end
844
+ if !@_request.respond_to?(:_brick_content_security_policy)
845
+ ::ActionDispatch::ContentSecurityPolicy::Request.module_exec do
846
+ alias :_brick_content_security_policy :content_security_policy
847
+ def content_security_policy
848
+ # Add appropriate hashes for inline styles to the content-security-policy if needed
849
+ if (cspd = (csp = _brick_content_security_policy).directives.fetch('style-src', nil)) && env['_is_brick']
850
+ cspd.select! { |val| val == \"'self'\" }
851
+ if params['action'] == 'show'
852
+ cspd << \"'unsafe-inline'\"
853
+ else
854
+ csp.instance_variable_get(:@_brick_style_shas).each { |s| cspd << s }
855
+ cspd << \"'unsafe-hashes'\"
856
+ end
857
+ cspd << 'https://cdn.jsdelivr.net'
858
+ end
859
+ if (cspsd = csp.directives.fetch('script-src', nil))
860
+ cspsd.select! { |val| val == \"'self'\" }
861
+ cspsd << \"'unsafe-inline'\"
862
+ cspsd << 'https://cdn.jsdelivr.net'
863
+ end
864
+ if (cspcd = csp.directives.fetch('connect-src', nil))
865
+ cspcd.select! { |val| val == \"'self'\" }
866
+ cspcd << 'https://cdn.jsdelivr.net'
867
+ end
868
+ csp
869
+ end
870
+
871
+ alias :_brick_content_security_policy_nonce :content_security_policy_nonce
872
+ def content_security_policy_nonce
873
+ _brick_content_security_policy_nonce unless env['_is_brick']
874
+ end
875
+ end
876
+ end
877
+ end
878
+
831
879
  # Accommodate composite primary keys that include strings with forward-slash characters
832
880
  def slashify(*vals)
833
881
  vals.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
@@ -1091,7 +1139,7 @@ end
1091
1139
  </script>
1092
1140
  <% end
1093
1141
  # SEARCH BOX
1094
- if @_brick_es&.index('r') # Must have at least Elasticsearch Read access %>
1142
+ if @_brick_es && @_brick_es&.index('r') # Must have at least Elasticsearch Read access %>
1095
1143
  <input type=\"text\" id=\"esSearch\" class=\"dimmed\">
1096
1144
  <script>
1097
1145
  var esSearch = document.getElementById(\"esSearch\");
@@ -1498,9 +1546,9 @@ end
1498
1546
  <tr><td<%= ' class=\"orphan\"'.html_safe if err_msg %>><%= err_msg || '(none)' %></td></tr>
1499
1547
  <% else
1500
1548
  collection2.each do |br_#{hm_singular_name}| %>
1501
- <tr><td><%= br_descrip = if (dc = descrip_cols&.first&.first&.last) && br_#{hm_singular_name}.respond_to?(dc)
1549
+ <tr><td><%= br_descrip = if br_#{hm_singular_name}.respond_to?(descrip_cols&.first&.first&.last)
1502
1550
  br_#{hm_singular_name}.brick_descrip(
1503
- descrip_cols&.first&.map { |col| br_#{hm_singular_name}.send(col.last) }
1551
+ #{hm.first.klass.name}._brick_deserialized_value(br_#{hm_singular_name}, descrip_cols&.first)
1504
1552
  )
1505
1553
  else # If the HM association has a scope, might not have picked up our SELECT detail
1506
1554
  pks = (klass = br_#{hm_singular_name}.class).primary_key
@@ -150,12 +150,11 @@ module Brick::Rails::FormTags
150
150
  end
151
151
  end
152
152
  br_descrip_args = [obj]
153
- # 0..62 because Postgres column names are limited to 63 characters
154
- br_descrip_args += [descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, bt_id_col] if descrips
153
+ br_descrip_args += [bt_class._brick_deserialized_value(obj, descrips[0..-2]), bt_id_col] if descrips
155
154
  bt_txt = bt_class.brick_descrip(*br_descrip_args)
156
155
  bt_txt = ::Brick::Rails.display_binary(bt_txt).html_safe if bt_txt&.encoding&.name == 'ASCII-8BIT'
157
156
  bt_txt ||= "<span class=\"orphan\">&lt;&lt; Orphaned ID: #{val} >></span>" if val
158
- bt_id = bt_id_col&.map { |id_col| obj.respond_to?(id_sym = id_col.to_sym) ? obj.send(id_sym) : id_col }
157
+ bt_id = bt_class._brick_deserialized_id(obj, bt_id_col)
159
158
  out << (bt_id&.first ? link_to(bt_txt, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, bt_id)) : bt_txt || '')
160
159
  end
161
160
  elsif (hms_col = hms_cols[col_name])
@@ -165,15 +164,16 @@ module Brick::Rails::FormTags
165
164
  hm_klass = (col = cols[col_name])[1]
166
165
  if col[2] == 'HO'
167
166
  descrips = bt_descrip[col_name.to_sym][hm_klass]
168
- if (ho_id = (ho_id_col = descrips.last).map { |id_col| obj.send(id_col.to_sym) })&.first
167
+ if (descrip_parts = hm_klass._brick_deserialized_value(obj, descrips[0..-2]))&.first
169
168
  ho_txt = if hm_klass.name == 'ActiveStorage::Attachment'
170
169
  begin
171
170
  ::Brick::Rails.display_binary(obj.send(col[3])&.blob&.download)&.html_safe
172
171
  rescue
173
172
  end
174
173
  else
175
- hm_klass.brick_descrip(obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, ho_id_col)
174
+ hm_klass.brick_descrip(obj, descrip_parts, ho_id_col = descrips.last)
176
175
  end
176
+ ho_id = hm_klass._brick_deserialized_id(obj, ho_id_col)
177
177
  out << link_to(ho_txt, send("#{hm_klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
178
178
  end
179
179
  elsif obj.respond_to?(ct_col = hms_col[1].to_sym) && (ct = obj.send(ct_col)&.to_i)&.positive?
@@ -267,11 +267,15 @@ module Brick::Rails::FormTags
267
267
  { sql_type: 'binary', type: :files }
268
268
  elsif rtans&.key?(k) # has_rich_text
269
269
  k = rtans[k]
270
- { sql_type: 'varchar', type: :text }
270
+ { sql_type: ActiveRecord::Base.connection.adapter_name == 'SQLite' ? 'string' : 'varchar', type: :text }
271
271
  end
272
- col = (ActiveRecord::ConnectionAdapters::Column.new(
273
- '', nil, ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(**kwargs)
274
- )) if kwargs
272
+ if kwargs
273
+ last_params = [nil, ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(**kwargs)]
274
+ # Rails 8.1 introduces the "cast_type" parameter coming in second after the name, making a total of 4 parameters
275
+ # (See: https://github.com/rails/rails/commit/9ad36e067222478090b36a985090475bb03e398c)
276
+ last_params.unshift(ActiveRecord::Type.lookup(kwargs[:sql_type].to_sym)) if ActiveRecord::ConnectionAdapters::Column.instance_methods.include?(:cast_type)
277
+ col = ActiveRecord::ConnectionAdapters::Column.new('', *last_params)
278
+ end
275
279
  end
276
280
  val = obj.attributes[k]
277
281
  out << "
@@ -309,7 +313,7 @@ module Brick::Rails::FormTags
309
313
  collection&.brick_(:each) do |obj|
310
314
  option_detail << [
311
315
  obj.brick_descrip(
312
- descrip_cols&.first&.map { |col2| obj.send(col2.last) },
316
+ bt_class._brick_deserialized_value(obj, descrip_cols&.first),
313
317
  obj_pk
314
318
  ), obj.send(obj_pk)
315
319
  ]
data/lib/brick/rails.rb CHANGED
@@ -80,7 +80,7 @@ module ::Brick::Rails
80
80
  end
81
81
  end
82
82
 
83
- def display_binary(val, max_size = 100_000)
83
+ def display_binary(val, max_size = 100_000, no_escaped_html = nil)
84
84
  return unless val
85
85
 
86
86
  @image_signatures ||= { (+"\xFF\xD8\xFF\xEE").force_encoding('ASCII-8BIT') => 'jpeg',
@@ -111,7 +111,8 @@ module ::Brick::Rails
111
111
  val.length < max_size
112
112
  "<img src=\"data:image/#{signature.last};base64,#{Base64.encode64(val)}\">"
113
113
  else
114
- "&lt;&nbsp;#{signature ? "#{signature} image" : 'Binary'}, #{val.length} bytes&nbsp;>"
114
+ descrip = "#{signature ? "#{signature} image" : 'Binary'}, #{val.length} bytes"
115
+ no_escaped_html ? "< #{descrip} >" : "&lt;&nbsp;#{descrip}&nbsp;>"
115
116
  end
116
117
  end
117
118
 
@@ -178,7 +179,7 @@ erDiagram
178
179
  'timestamp without time zone' => 'timestamp',
179
180
  'timestamp with time zone' => 'timestamp',
180
181
  'time without time zone' => 'time',
181
- 'time with time zone' => 'time' }[dt] || dt&.tr(' ', '_') || 'int'
182
+ 'time with time zone' => 'time' }[dt] || dt&.tr(' (', '_').tr(')', '') || 'int'
182
183
  end
183
184
  callbacks.merge({#{model_short_name.inspect} => ::#{model.name}}).each do |cb_k, cb_class|
184
185
  cb_relation = ::Brick.relations[cb_class.table_name]
@@ -222,7 +223,7 @@ erDiagram
222
223
  '(hidden)'
223
224
  else
224
225
  if val.is_a?(String)
225
- return ::Brick::Rails.display_binary(val) unless (val_utf8 = val.dup.force_encoding('UTF-8')).valid_encoding?
226
+ return ::Brick::Rails.display_binary(val, 100_000, is_xml == false) unless (val_utf8 = val.dup.force_encoding('UTF-8')).valid_encoding?
226
227
 
227
228
  val = val_utf8.strip
228
229
  return CGI.escapeHTML(val) if is_xml
@@ -540,10 +541,10 @@ input+svg.revert {
540
541
 
541
542
  AA_PNG = "<img src=\"data:image/png;base64,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\">
542
543
  "
543
-
544
+ IN_APP_STYLE = 'fill:rgb(31,147,209);'
544
545
  IN_APP = "<svg height=\"36px\" viewBox=\"0 0 214 274\" xmlns=\"http://www.w3.org/2000/svg\">
545
546
  <g transform=\"matrix(4.16667,0,0,4.16667,-1049.47,-789.371)\">
546
- <path d=\"M281.386,193.134L287.086,193.134C287.433,193.134 287.716,193.417 287.716,193.764C287.716,194.11 287.433,194.394 287.086,194.394L281.386,194.394C281.04,194.394 280.756,194.11 280.756,193.764C280.756,193.417 281.04,193.134 281.386,193.134ZM284.252,245.638C282.456,245.638 281.008,247.086 281.008,248.851C281.008,250.645 282.456,252.094 284.252,252.094C286.047,252.094 287.496,250.645 287.496,248.851C287.496,247.086 286.047,245.638 284.252,245.638ZM284.252,246.331C282.835,246.331 281.701,247.465 281.701,248.851C281.701,250.268 282.835,251.401 284.252,251.401C285.638,251.401 286.803,250.268 286.803,248.851C286.803,247.465 285.638,246.331 284.252,246.331ZM275.843,208.63L287.559,218.11L275.843,227.622L275.843,221.858L251.874,221.858L251.874,214.394L275.843,214.394L275.843,208.63ZM278.866,239.906L278.866,233.102C278.866,232.851 278.677,232.693 278.456,232.693L271.622,232.693C271.401,232.693 271.212,232.851 271.212,233.102L271.212,239.906C271.212,240.157 271.401,240.314 271.622,240.314L278.456,240.314C278.677,240.314 278.866,240.157 278.866,239.906ZM280.41,233.102L280.41,239.906C280.41,240.157 280.599,240.314 280.819,240.314L287.653,240.314C287.874,240.314 288.063,240.157 288.063,239.906L288.063,233.102C288.063,232.851 287.874,232.693 287.653,232.693L280.819,232.693C280.599,232.693 280.41,232.851 280.41,233.102ZM289.606,233.102L289.606,239.906C289.606,240.157 289.795,240.314 290.047,240.314L296.851,240.314C297.071,240.314 297.26,240.157 297.26,239.906L297.26,233.102C297.26,232.851 297.071,232.693 296.851,232.693L290.047,232.693C289.795,232.693 289.606,232.851 289.606,233.102ZM269.197,189.449L299.276,189.449C301.354,189.449 303.055,191.149 303.055,193.197L303.055,251.275C303.055,253.355 301.354,255.055 299.276,255.055L269.197,255.055C267.118,255.055 265.449,253.355 265.449,251.275L265.449,223.496L267.842,223.496L267.842,242.488L300.63,242.488L300.63,198.394L267.842,198.394L267.842,212.725L265.449,212.725L265.449,193.197C265.449,191.149 267.118,189.449 269.197,189.449Z\" style=\"fill:rgb(31,147,209);\"/>
547
+ <path d=\"M281.386,193.134L287.086,193.134C287.433,193.134 287.716,193.417 287.716,193.764C287.716,194.11 287.433,194.394 287.086,194.394L281.386,194.394C281.04,194.394 280.756,194.11 280.756,193.764C280.756,193.417 281.04,193.134 281.386,193.134ZM284.252,245.638C282.456,245.638 281.008,247.086 281.008,248.851C281.008,250.645 282.456,252.094 284.252,252.094C286.047,252.094 287.496,250.645 287.496,248.851C287.496,247.086 286.047,245.638 284.252,245.638ZM284.252,246.331C282.835,246.331 281.701,247.465 281.701,248.851C281.701,250.268 282.835,251.401 284.252,251.401C285.638,251.401 286.803,250.268 286.803,248.851C286.803,247.465 285.638,246.331 284.252,246.331ZM275.843,208.63L287.559,218.11L275.843,227.622L275.843,221.858L251.874,221.858L251.874,214.394L275.843,214.394L275.843,208.63ZM278.866,239.906L278.866,233.102C278.866,232.851 278.677,232.693 278.456,232.693L271.622,232.693C271.401,232.693 271.212,232.851 271.212,233.102L271.212,239.906C271.212,240.157 271.401,240.314 271.622,240.314L278.456,240.314C278.677,240.314 278.866,240.157 278.866,239.906ZM280.41,233.102L280.41,239.906C280.41,240.157 280.599,240.314 280.819,240.314L287.653,240.314C287.874,240.314 288.063,240.157 288.063,239.906L288.063,233.102C288.063,232.851 287.874,232.693 287.653,232.693L280.819,232.693C280.599,232.693 280.41,232.851 280.41,233.102ZM289.606,233.102L289.606,239.906C289.606,240.157 289.795,240.314 290.047,240.314L296.851,240.314C297.071,240.314 297.26,240.157 297.26,239.906L297.26,233.102C297.26,232.851 297.071,232.693 296.851,232.693L290.047,232.693C289.795,232.693 289.606,232.851 289.606,233.102ZM269.197,189.449L299.276,189.449C301.354,189.449 303.055,191.149 303.055,193.197L303.055,251.275C303.055,253.355 301.354,255.055 299.276,255.055L269.197,255.055C267.118,255.055 265.449,253.355 265.449,251.275L265.449,223.496L267.842,223.496L267.842,242.488L300.63,242.488L300.63,198.394L267.842,198.394L267.842,212.725L265.449,212.725L265.449,193.197C265.449,191.149 267.118,189.449 269.197,189.449Z\" style=\"#{IN_APP_STYLE}\"/>
547
548
  </g>
548
549
  </svg>"
549
550
  end
@@ -517,7 +517,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
517
517
  end
518
518
  end
519
519
 
520
- if (ems = ::Brick.elasticsearch_models)
520
+ if (ems = ::Brick.elasticsearch_models && Object.const_defined?('Elastic'))
521
521
  access = case ems
522
522
  when Hash, String # Hash is a list of resource names and ES permissions such as 'r' or 'icr'
523
523
  ems
@@ -553,7 +553,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
553
553
  end
554
554
  s[rel.first] = perms
555
555
  end
556
- else # or there are specific permissions for each resource, so find the matching indexes
556
+ else # or there are specific permissions for each resource, so find the matching indexes.
557
557
  client = Elastic::Transport::Client.new
558
558
  ::Brick.elasticsearch_existings.each_with_object({}) do |index, s|
559
559
  this_access = access.is_a?(String) ? access : access[index.first] || '' # Look up permissions from above
@@ -625,13 +625,16 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
625
625
  end
626
626
 
627
627
  def ar_tables
628
- ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
629
- ActiveRecord::Base.schema_migrations_table_name
630
- else
631
- 'schema_migrations'
632
- end
633
628
  ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : 'ar_internal_metadata'
634
- [ar_smtn, ar_imtn]
629
+ [self._schema_migrations_table_name, ar_imtn]
630
+ end
631
+
632
+ def _schema_migrations_table_name
633
+ if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
634
+ ActiveRecord::Base.schema_migrations_table_name
635
+ else
636
+ 'schema_migrations'
637
+ end
635
638
  end
636
639
  end
637
640
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 242
8
+ TINY = 244
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
@@ -150,7 +150,7 @@ module Brick
150
150
  when 1
151
151
  ".
152
152
  # # Here is a possible polymorphic association that has been auto-identified for the #{current_db} database:
153
- # Brick.polymorphics = { #{possible_additional_references.first} }"
153
+ # Brick.polymorphics = { #{possible_polymorphics.first} }"
154
154
 
155
155
  else
156
156
  ".
@@ -248,20 +248,24 @@ module Brick
248
248
  }:"
249
249
  pp stuck_sorted[0..4]
250
250
  elsif do_schema_migrations # Successful, and now we can update the schema_migrations table accordingly
251
- unless ActiveRecord::Migration.table_exists?(ActiveRecord::Base.schema_migrations_table_name)
252
- ActiveRecord::SchemaMigration.create_table
253
- end
254
- # Remove to_delete - to_create
255
- if ((versions_to_delete_or_append ||= []) - versions_to_create).present? && is_delete_versions
256
- ActiveRecord::Base.execute_sql("DELETE FROM #{
257
- ActiveRecord::Base.schema_migrations_table_name} WHERE version IN (#{
258
- (versions_to_delete_or_append - versions_to_create).map { |vtd| "'#{vtd}'" }.join(', ')}
259
- )")
251
+ smtn = ::Brick._schema_migrations_table_name
252
+ if ActiveRecord::Migration.table_exists?(smtn)
253
+ # Remove to_delete - to_create
254
+ if ((versions_to_delete_or_append ||= []) - versions_to_create).present? && is_delete_versions
255
+ ActiveRecord::Base.execute_sql("DELETE FROM #{
256
+ smtn} WHERE version IN (#{
257
+ (versions_to_delete_or_append - versions_to_create).map { |vtd| "'#{vtd}'" }.join(', ')}
258
+ )")
259
+ end
260
+ else
261
+ ActiveRecord::Base.connection.create_table(smtn, primary_key: 'version') do |t|
262
+ t.string :verison
263
+ end
260
264
  end
261
265
  # Add to_create - to_delete
262
- if is_insert_versions && ((versions_to_create ||= []) - versions_to_delete_or_append).present?
266
+ if is_insert_versions && (versions_to_delete_or_append ||= []) && ((versions_to_create ||= []) - versions_to_delete_or_append).present?
263
267
  ActiveRecord::Base.execute_sql("INSERT INTO #{
264
- ActiveRecord::Base.schema_migrations_table_name} (version) VALUES #{
268
+ smtn} (version) VALUES #{
265
269
  (versions_to_create - versions_to_delete_or_append).map { |vtc| "('#{vtc}')" }.join(', ')
266
270
  }")
267
271
  end
@@ -13,8 +13,8 @@ module Brick
13
13
  end
14
14
 
15
15
  def generate_seeds(relations = nil)
16
- if File.exist?(seed_file_path = "#{::Rails.root}/db/seeds.rb")
17
- puts "WARNING: seeds file #{seed_file_path} appears to already be present.\nOverwrite?"
16
+ if File.exist?((seed_file_path = "#{::Rails.root}/db/seed") + 's.rb')
17
+ puts "WARNING: seeds file #{seed_file_path}s.rb appears to already be present.\nOverwrite?"
18
18
  return unless gets_list(list: ['No', 'Yes']) == 'Yes'
19
19
 
20
20
  puts "\n"
@@ -56,11 +56,15 @@ module Brick
56
56
  end
57
57
  end
58
58
  end
59
- seeds = +'# Seeds file for '
59
+ batch_num = 1
60
+ seeds_file = File.open("#{seed_file_path}s.rb", 'w')
61
+ seeds_len = lines_len = 0
62
+ seeds_file.write('# Seeds file for ')
63
+ seed_rows = nil
60
64
  if (arbc = ActiveRecord::Base.connection).respond_to?(:current_database) # SQLite3 can't do this!
61
- seeds << "#{arbc.current_database}:\n"
65
+ seeds_file.write("#{arbc.current_database}:\n")
62
66
  elsif (filename = arbc.instance_variable_get(:@connection_parameters)&.fetch(:database, nil))
63
- seeds << "#{filename}:\n"
67
+ seeds_file.write("#{filename}:\n")
64
68
  end
65
69
  done = []
66
70
  fks = {}
@@ -93,7 +97,6 @@ module Brick
93
97
  end
94
98
  end
95
99
  ).present?
96
- seeds << "\n"
97
100
  unless is_airtable
98
101
  # Search through the fringe to see if we should bump special dependent classes forward to the next fringe.
99
102
  # (Currently only ActiveStorage::Attachment if there's also an ActiveStorage::VariantRecord in the same
@@ -150,8 +153,11 @@ module Brick
150
153
  ::Brick::AirtableApiCaller.https_get("https://api.airtable.com/v0/#{airtable_table.base_id}/#{airtable_table.id}").fetch('records', nil)
151
154
  end
152
155
  else
153
- klass.order(*pkey_cols)
156
+ # Must convert to a symbol when doing ORDER BY due to this ActiveRecord nuance:
157
+ # https://github.com/rails/rails/issues/2601
158
+ klass.order(*pkey_cols.map(&:to_sym)) unless relation[:pkey].empty?
154
159
  end
160
+ seed_rows = +''
155
161
  collection&.each do |obj|
156
162
  if is_airtable
157
163
  fields = obj['fields'].each_with_object({}) do |field, s|
@@ -177,11 +183,13 @@ module Brick
177
183
  end
178
184
  unless has_rows
179
185
  has_rows = true
180
- seeds << " puts 'Seeding: #{klass_name}'\n"
186
+ seed_rows << "\n puts 'Seeding: #{klass_name}'\n"
187
+ puts "Preparing: #{klass_name} — #{batch_num}"
181
188
  end
182
189
  is_empty = false
183
190
  # For Airtable, take off the "rec___" prefix
184
191
  pk_val = is_airtable ? airtable_id[3..-1] : brick_escape(orig_pk_val = obj.attributes_before_type_cast[pkey_cols.first])
192
+ pk_val = -pk_val if pk_val.is_a?(Numeric) && pk_val < 0
185
193
  var_name = "#{tbl.singularize.gsub('.', '__')}_#{pk_val}"
186
194
  fk_vals = []
187
195
  data = []
@@ -196,8 +204,12 @@ module Brick
196
204
  # Used to be: obj.send(col)
197
205
  # (and with that it was possible to raise ActiveRecord::Encryption::Errors::Configuration...)
198
206
  # %%% should test further and see if that is possible with this code!)
199
- if (val = obj.attributes_before_type_cast[col]) && (val.is_a?(Time) || val.is_a?(Date))
200
- val = val.to_s
207
+ if (val = obj.attributes_before_type_cast[col])
208
+ if (val.is_a?(Time) || val.is_a?(Date))
209
+ val = val.to_s
210
+ elsif val.is_a?(Numeric)
211
+ val = -val if val < 0
212
+ end
201
213
  end
202
214
  if fk
203
215
  fk_val = if is_airtable
@@ -240,12 +252,12 @@ module Brick
240
252
  record_id = data.find { |d| d.start_with?('record_id: ') }[11..-1]
241
253
  data.reject! { |d| d.start_with?('record_id: ') || d.start_with?('created_at: ') || d.start_with?('updated_at: ') }
242
254
  data << "record_id: atrt_ids[[#{record_id}, '#{record_class}']]"
243
- seeds << "#{var_name} = #{klass_name}.find_or_create_by(#{(fk_vals + data).join(', ')}) do |asa|
255
+ seed_rows << "#{var_name} = #{klass_name}.find_or_create_by(#{(fk_vals + data).join(', ')}) do |asa|
244
256
  asa.created_at = DateTime.parse('#{obj.created_at.inspect}')#{"
245
257
  asa.updated_at = DateTime.parse('#{obj.updated_at.inspect}')" if obj.respond_to?(:updated_at)}
246
258
  end\n"
247
259
  else
248
- seeds << "#{var_name} = #{klass_name}.create(#{(fk_vals + data).join(', ')})\n"
260
+ seed_rows << "#{var_name} = #{klass_name}.create(#{(fk_vals + data).join(', ')})\n"
249
261
  unless is_airtable
250
262
  klass.attachment_reflections.each do |k, v|
251
263
  if (attached = obj.send(k))
@@ -255,10 +267,24 @@ end\n"
255
267
  end if klass.respond_to?(:attachment_reflections)
256
268
  end
257
269
  end
258
- updates.each { |update| seeds << update } # Anything that needs patching up after-the-fact
270
+ updates.each { |update| seed_rows << update } # Anything that needs patching up after-the-fact
271
+ seeds_file.write(seed_rows)
272
+ seeds_len += seed_rows.length
273
+ if (lines_len += 1) > 50000 || # More than 50K rows? ...
274
+ seeds_len > 98000000 # ... or 100 megs total? ...
275
+ # ... then break it into multiple files
276
+ seeds_file.close
277
+ if batch_num == 1 # First one? Then rename file to seedsx_0001.rb
278
+ File.rename("#{seed_file_path}s.rb", "#{seed_file_path}x_0001.rb")
279
+ end
280
+ seeds_file = File.open("#{seed_file_path}x_#{(batch_num += 1).to_s.rjust(4, '0')}.rb", 'w')
281
+ puts "=== File #{batch_num} ===\n"
282
+ seeds_len = lines_len = 0
283
+ end
284
+ seed_rows = +''
259
285
  end
260
286
  unless has_rows || klass_name.nil?
261
- seeds << " # (Skipping #{klass_name} as it has no rows)\n"
287
+ seeds_file.write(" # (Skipping #{klass_name} as it has no rows)\n")
262
288
  end
263
289
  end
264
290
  done.concat(fringe)
@@ -271,17 +297,28 @@ end\n"
271
297
  dupe_table != table_name && assoc_pairs.length > 0 && assoc_pairs.length == dupe_pairs.length &&
272
298
  dupe_pairs.all? { |pair_name, _v3| assoc_pairs.keys.include?(pair_name) }
273
299
  end
274
- seeds << " if ActiveRecord::Migration.table_exists?('#{table_name}')\n"
275
- seeds << " # Duplicate data found in: #{dupes.keys.join(', ')}\n" unless dupes.empty?
276
- seeds << " puts 'Seeding Airtable associations for #{table_name.camelize.singularize}'\n"
300
+ seed_rows << " if ActiveRecord::Migration.table_exists?('#{table_name}')\n"
301
+ seed_rows << " # Duplicate data found in: #{dupes.keys.join(', ')}\n" unless dupes.empty?
302
+ seed_rows << " puts 'Seeding Airtable associations for #{table_name.camelize.singularize}'\n"
277
303
  assoc_pairs.each do |_k2, pair_values|
278
- seeds << "#{table_name.singularize.camelize}.create(#{pair_values})\n"
304
+ seed_rows << "#{table_name.singularize.camelize}.create(#{pair_values})\n"
279
305
  end
280
- seeds << " end\n"
306
+ seed_rows << " end\n"
281
307
  airtable_assocs_done[table_name] = assoc_pairs
282
308
  end
309
+ seeds_file.write(seed_rows) unless seed_rows.blank?
310
+
311
+ if batch_num > 1
312
+ seeds_file.close
313
+
314
+ # Create a main seeds file in place of the one that was renamed out
315
+ File.open("#{seed_file_path}s.rb", 'w') do |main_seeds_file|
316
+ (1..batch_num).each do |num|
317
+ main_seeds_file.write("load 'db/seedx_#{num.to_s.rjust(4, '0')}.rb'\n")
318
+ end
319
+ end
320
+ end
283
321
 
284
- File.open(seed_file_path, "w") { |f| f.write seeds }
285
322
  stuck_counts = Hash.new { |h, k| h[k] = 0 }
286
323
  chosen.each do |leftover|
287
324
  puts "Can't do #{leftover.klass.name} because:\n #{stuck[leftover.table_name].map do |snag|
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.242
4
+ version: 1.0.244
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-20 00:00:00.000000000 Z
11
+ date: 2026-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord