brick 1.0.112 → 1.0.114

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: 69051263cec00b3672b8d48b0c595ff6875b81f27e7995be72a03d6699ad6d02
4
- data.tar.gz: 217687ad2f6b93c33dc781f5930a0475a60fb27d782593109055beaae19f4e55
3
+ metadata.gz: 5006a6dabf5c9e86965e57a40ae344f9d67c2fc5e76caec8a00e978117089062
4
+ data.tar.gz: 5bd0fef75e305514b4ef22e4930660cdbf9385c22b07ee92d6eb77a626a13943
5
5
  SHA512:
6
- metadata.gz: 6eff4c8a9f59673d14be1d890b067e17e631b1d6938a22b81c1ec45c3785489195d5d9e93939a38c681d36d844df2ad9a932584d8269b0fd2ef678de428c805c
7
- data.tar.gz: 723425258d449708f18e086f289147b3402f728c93fbcba3a16654c3d15b4fe715022e427cb3f87e731bf9b73d693a5d4f43a500bdbde3641b69ac61e4c35e35
6
+ metadata.gz: b0621761d15baec575a9cd16fdb1b22e9a1d4784308c071d9878121ac841f6cf7419cb6fbcc210afad2ce98a69814c27279dc951a5d84750e7573e203d2f806a
7
+ data.tar.gz: ed4b5c992076b4f28c85ab8af48bab847ab80105f2999371475529219cc6b0053444dc722a700d686fc7ed7ea39d3d939cb448c43d798bde18a0b5cfd80be23e
data/lib/brick/config.rb CHANGED
@@ -213,6 +213,14 @@ module Brick
213
213
  @mutex.synchronize { @polymorphics = polys }
214
214
  end
215
215
 
216
+ def json_columns
217
+ @mutex.synchronize { @json_columns ||= {} }
218
+ end
219
+
220
+ def json_columns=(cols)
221
+ @mutex.synchronize { @json_columns = cols }
222
+ end
223
+
216
224
  def model_descrips
217
225
  @mutex.synchronize { @model_descrips ||= {} }
218
226
  end
@@ -274,7 +274,7 @@ module ActiveRecord
274
274
  tbl_parts.unshift(::Brick.config.path_prefix) if ::Brick.config.path_prefix
275
275
  index = tbl_parts.map(&:underscore).join(separator)
276
276
  # Rails applies an _index suffix to that route when the resource name is singular
277
- index << '_index' if mode != :singular && index == index.singularize
277
+ index << '_index' if mode != :singular && separator == '_' && index == index.singularize
278
278
  index
279
279
  end
280
280
 
@@ -1602,11 +1602,11 @@ class Object
1602
1602
  else
1603
1603
  relation.last[:cols]
1604
1604
  end
1605
- { :index => [:get, 'list'], :create => [:post, 'create a'] }.each do |k, v|
1605
+ { :index => [:get, 'list', true], :create => [:post, 'create a', false] }.each do |k, v|
1606
1606
  unless actions&.exclude?(k)
1607
1607
  this_resource = (s["#{current_api_root}#{relation_name}"] ||= {})
1608
1608
  this_resource[v.first] = {
1609
- 'summary': "#{v[1]} #{relation.first}",
1609
+ 'summary': "#{v[1]} #{relation.first.send(v[2] ? :pluralize : :singularize)}",
1610
1610
  'description': table_description,
1611
1611
  'parameters': renamed_columns.map do |k2, v2|
1612
1612
  param = { in: 'query', 'name': k2, 'schema': { 'type': v2.first } }
@@ -1841,7 +1841,19 @@ class Object
1841
1841
  end
1842
1842
 
1843
1843
  instance_variable_set("@#{singular_table_name}".to_sym, (obj = find_obj))
1844
- obj.send(:update, send(params_name_sym))
1844
+ upd_params = send(params_name_sym)
1845
+ if (json_cols = model.columns.select { |c| c.type == :json }.map(&:name)).present?
1846
+ upd_hash = upd_params.to_h
1847
+ json_cols.each do |c|
1848
+ begin
1849
+ upd_hash[c] = JSON.parse(upd_hash[c]) # At least attempt to turn this into a parsed hash or array object
1850
+ rescue
1851
+ end
1852
+ end
1853
+ obj.send(:update, upd_hash)
1854
+ else
1855
+ obj.send(:update, upd_params)
1856
+ end
1845
1857
  end
1846
1858
 
1847
1859
  code << " def destroy\n"
@@ -1865,11 +1877,14 @@ class Object
1865
1877
  @#{singular_table_name} = #{model.name}.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
1866
1878
  end\n"
1867
1879
  self.define_method :find_obj do
1868
- id = if model.columns_hash[pk.first]&.type == :string
1869
- is_pk_string = true
1880
+ id = if pk.length == 1 # && model.columns_hash[pk.first]&.type == :string
1870
1881
  params[:id].gsub('^^sl^^', '/')
1871
1882
  else
1872
- params[:id]&.split(/[\/,_]/).map do |val_part|
1883
+ if model.columns_hash[pk.first]&.type == :string
1884
+ params[:id]&.split('/')
1885
+ else
1886
+ params[:id]&.split(/[\/,_]/)
1887
+ end.map do |val_part|
1873
1888
  val_part.gsub('^^sl^^', '/')
1874
1889
  end
1875
1890
  end
@@ -245,7 +245,7 @@ window.addEventListener(\"popstate\", linkSchemas);
245
245
  @_name = name || ''
246
246
  end
247
247
  def to_s
248
- @_name.html_safe + @vc.instance_variable_get(:@__vc_helpers)&.link_to_brick(nil,
248
+ @_name.to_s.html_safe + @vc.instance_variable_get(:@__vc_helpers)&.link_to_brick(nil,
249
249
  "<svg version=\"1.1\" style=\"display: inline; padding-left: 0.5em;\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"
250
250
  viewBox=\"0 0 58 58\" height=\"1.4em\" xml:space=\"preserve\">
251
251
  <g>
@@ -304,6 +304,26 @@ window.addEventListener(\"popstate\", linkSchemas);
304
304
  end
305
305
  end # Avo compatibility
306
306
 
307
+ # ActiveAdmin compatibility
308
+ if Object.const_defined?('ActiveAdmin') && ::ActiveAdmin.application&.site_title.present?
309
+ ::ActiveAdmin.class_exec do
310
+ class << self
311
+ ActiveAdmin.load!
312
+ alias _brick_routes routes
313
+ def routes(*args)
314
+ ::Brick.relations.each do |k, v|
315
+ next if k == 'active_admin_comments'
316
+
317
+ if (class_name = Object.const_get(v.fetch(:class_name, nil)))
318
+ ::ActiveAdmin.register(class_name) { config.clear_batch_actions! }
319
+ end
320
+ end
321
+ _brick_routes(*args)
322
+ end
323
+ end
324
+ end
325
+ end
326
+
307
327
  # ====================================
308
328
  # Dynamically create generic templates
309
329
  # ====================================
@@ -648,15 +668,19 @@ input+svg.revert {
648
668
  </style>
649
669
 
650
670
  <% is_includes_dates = nil
671
+ is_includes_json = nil
651
672
  def is_bcrypt?(val)
652
673
  val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
653
674
  end
654
- def hide_bcrypt(val, max_len = 200)
675
+ def hide_bcrypt(val, is_xml = nil, max_len = 200)
655
676
  if is_bcrypt?(val)
656
677
  '(hidden)'
657
678
  else
658
679
  if val.is_a?(String)
659
- if (val = val.dup.strip).length > max_len
680
+ val = val.dup.force_encoding('UTF-8').strip
681
+ return CGI.escapeHTML(val) if is_xml
682
+
683
+ if val.length > max_len
660
684
  if val[0] == '<' # Seems to be HTML?
661
685
  cur_len = 0
662
686
  cur_idx = 0
@@ -703,7 +727,6 @@ def hide_bcrypt(val, max_len = 200)
703
727
  end
704
728
  val = \"#\{val}...\"
705
729
  end
706
- val = val.dup.force_encoding('UTF-8') unless val.encoding.name == 'UTF-8'
707
730
  val
708
731
  else
709
732
  val.to_s
@@ -768,7 +791,7 @@ def display_value(col_type, val)
768
791
  display_binary(val) if val
769
792
  else
770
793
  if col_type
771
- hide_bcrypt(val)
794
+ hide_bcrypt(val, col_type == :xml)
772
795
  else
773
796
  '?'
774
797
  end
@@ -1362,7 +1385,7 @@ erDiagram
1362
1385
  <% end %>
1363
1386
  </table>
1364
1387
  <%
1365
- if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
1388
+ if (description = (relation = Brick.relations[tbl_name = #{model_name}.table_name])&.fetch(:description, nil)) %><%=
1366
1389
  description %><br><%
1367
1390
  end
1368
1391
  %><%= link_to '(See all #{obj_name.pluralize})', #{@_brick_model._brick_index}_path %>
@@ -1434,12 +1457,18 @@ end
1434
1457
  \"<span class=\\\"orphan\\\">Orphaned ID: #\{val}</span>\".html_safe
1435
1458
  end %>
1436
1459
  <% else
1437
- col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
1460
+ col_type = if ::Brick.config.json_columns[tbl_name]&.include?(k)
1461
+ :json
1462
+ elsif col&.sql_type == 'geography'
1463
+ col.sql_type
1464
+ else
1465
+ col&.type
1466
+ end
1438
1467
  case (col_type ||= col&.sql_type)
1439
1468
  when :string, :text %>
1440
1469
  <% if is_bcrypt?(val) # || .readonly?
1441
1470
  is_revert = false %>
1442
- <%= hide_bcrypt(val, 1000) %>
1471
+ <%= hide_bcrypt(val, nil, 1000) %>
1443
1472
  <% else %>
1444
1473
  <%= f.text_field(k.to_sym, html_options) %>
1445
1474
  <% end %>
@@ -1476,6 +1505,13 @@ end
1476
1505
  end %>
1477
1506
  <% when :primary_key
1478
1507
  is_revert = false %>
1508
+ <% when :json
1509
+ is_includes_json = true %>
1510
+ <%= # Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
1511
+ # (and previous to this, escape backticks with our own goofy code of ^^br_btick__ )
1512
+ val_str = val.is_a?(String) ? val : val.to_json # Clean up bogus JSON if necessary
1513
+ json_field = f.hidden_field k.to_sym, { class: 'jsonpicker', value: val_str.gsub('`', '^^br_btick__').tr('\"', '`').html_safe } %>
1514
+ <div id=\"_br_json_<%= f.field_id(k) %>\"></div>
1479
1515
  <% else %>
1480
1516
  <%= is_revert = false
1481
1517
  display_value(col_type, val).html_safe %>
@@ -1579,6 +1615,35 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1579
1615
  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdnjs.cloudflare.com/ajax/libs/slim-select/1.27.1/slimselect.min.css\">
1580
1616
  <% end %>
1581
1617
 
1618
+ <% # Started with v0.14.4 of vanilla-jsoneditor
1619
+ if is_includes_json %>
1620
+ <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor/themes/jse-theme-default.min.css\">
1621
+ <script type=\"module\">
1622
+ import { JSONEditor } from \"https://cdn.jsdelivr.net/npm/vanilla-jsoneditor/index.min.js\";
1623
+ document.querySelectorAll(\"input.jsonpicker\").forEach(function (inp) {
1624
+ var jsonDiv;
1625
+ if (jsonDiv = document.getElementById(\"_br_json_\" + inp.id)) {
1626
+ new JSONEditor({
1627
+ target: jsonDiv,
1628
+ props: {
1629
+ // Instead of text can also do: { json: JSONValue }
1630
+ // Other options: name: \"taco\", mode: \"tree\", navigationBar: false, mainMenuBar: false, statusBar: false, search: false, templates:, history: false
1631
+ content: {text: inp.value.replace(/`/g, '\"').replace(/\\^\\^br_btick__/g, \"`\")},
1632
+ onChange: (function (inp2) {
1633
+ return function (updatedContent, previousContent, contentErrors, patchResult) {
1634
+ // console.log('onChange', updatedContent.json, updatedContent.text);
1635
+ inp2.value = updatedContent.text || JSON.stringify(updatedContent.json);
1636
+ };
1637
+ })(inp)
1638
+ }
1639
+ });
1640
+ } else {
1641
+ console.log(\"Could not find JSON picker for \" + inp.id);
1642
+ }
1643
+ });
1644
+ </script>
1645
+ <% end %>
1646
+
1582
1647
  <% if true # @_brick_erd
1583
1648
  %>
1584
1649
  <script>
@@ -116,7 +116,7 @@ module Brick::Rails::FormTags
116
116
  end
117
117
  end
118
118
  elsif (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
119
- binding.pry if col.is_a?(Array)
119
+ # binding.pry if col.is_a?(Array)
120
120
  col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
121
121
  out << display_value(col_type || col&.sql_type, val).to_s
122
122
  elsif cust_col
@@ -201,7 +201,8 @@ module Brick::Rails::FormTags
201
201
  end
202
202
  filter = "?#{filter_parts.join('&')}" if filter_parts.present?
203
203
  app_routes = Rails.application.routes # In case we're operating in another engine, reference the application since Brick routes are placed there.
204
- relation = ::Brick.relations.fetch(rel_name || args.first.table_name, nil)
204
+ klass = klass_or_obj.is_a?(ActiveRecord::Base) ? klass_or_obj.class : klass_or_obj
205
+ relation = ::Brick.relations.fetch(rel_name || klass.table_name, nil)
205
206
  if (klass_or_obj&.is_a?(Class) && klass_or_obj < ActiveRecord::Base) ||
206
207
  (klass_or_obj&.is_a?(ActiveRecord::Base) && klass_or_obj.new_record? && (klass_or_obj = klass_or_obj.class))
207
208
  path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj, relation) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index(nil, '/', relation), action: :index)}#{filter}"
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 112
8
+ TINY = 114
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
@@ -458,6 +458,11 @@ module Brick
458
458
  Brick.config.polymorphics = polys || {}
459
459
  end
460
460
 
461
+ # @api public
462
+ def json_columns=(cols)
463
+ Brick.config.json_columns = cols
464
+ end
465
+
461
466
  # DSL templates for individual models to provide prettier descriptions of objects
462
467
  # @api public
463
468
  def model_descrips=(descrips)
@@ -529,7 +534,7 @@ module Brick
529
534
  You might be missing an STI namespace prefix entry for these tables: #{missing_stis.keys.join(', ')}.
530
535
  In config/initializers/brick.rb appropriate entries would look something like:
531
536
  Brick.sti_namespace_prefixes = {"
532
- puts missing_stis.map { |_k, missing_sti| "\n '::#{missing_sti}' => 'SomeParentModel'" }.join(',')
537
+ puts missing_stis.map { |_k, missing_sti| "\n '#{missing_sti}' => 'SomeParentModel'" }.join(',')
533
538
  puts " }
534
539
  (Just trade out SomeParentModel with some more appropriate one.)"
535
540
  end
@@ -654,205 +659,205 @@ In config/initializers/brick.rb appropriate entries would look something like:
654
659
 
655
660
  module RouteSet
656
661
  def finalize!
657
- # %%% Was: ::Rails.application.routes.named_routes.route_defined?(:brick_status_path)
658
- unless ::Rails.application.routes.named_routes.names.include?(:brick_status)
659
- path_prefix = ::Brick.config.path_prefix
660
- existing_controllers = routes.each_with_object({}) do |r, s|
661
- c = r.defaults[:controller]
662
- s[c] = nil if c
662
+ routeset_to_use = ::Rails.application.routes
663
+ return super unless self == routeset_to_use
664
+
665
+ path_prefix = ::Brick.config.path_prefix
666
+ existing_controllers = routes.each_with_object({}) do |r, s|
667
+ c = r.defaults[:controller]
668
+ s[c] = nil if c
669
+ end
670
+ append do
671
+ tables = []
672
+ views = []
673
+ table_class_length = 38 # Length of "Classes that can be built from tables:"
674
+ view_class_length = 37 # Length of "Classes that can be built from views:"
675
+
676
+ brick_namespace_create = lambda do |path_names, res_name, options|
677
+ if path_names&.present?
678
+ if (path_name = path_names.pop).is_a?(Array)
679
+ module_name = path_name[1]
680
+ path_name = path_name.first
681
+ end
682
+ send(:scope, { module: module_name || path_name, path: path_name, as: path_name }) do
683
+ brick_namespace_create.call(path_names, res_name, options)
684
+ end
685
+ else
686
+ send(:resources, res_name.to_sym, **options)
687
+ end
663
688
  end
664
- ::Rails.application.routes.append do
665
- tables = []
666
- views = []
667
- table_class_length = 38 # Length of "Classes that can be built from tables:"
668
- view_class_length = 37 # Length of "Classes that can be built from views:"
669
-
670
- brick_namespace_create = lambda do |path_names, res_name, options|
671
- if path_names&.present?
672
- if (path_name = path_names.pop).is_a?(Array)
673
- module_name = path_name[1]
674
- path_name = path_name.first
675
- end
676
- send(:scope, { module: module_name || path_name, path: path_name, as: path_name }) do
677
- brick_namespace_create.call(path_names, res_name, options)
678
- end
689
+
690
+ # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
691
+ # If auto-controllers and auto-models are both enabled then this makes sense:
692
+ controller_prefix = (path_prefix ? "#{path_prefix}/" : '')
693
+ sti_subclasses = ::Brick.config.sti_namespace_prefixes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
694
+ # Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
695
+ s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
696
+ end
697
+ versioned_views = {} # Track which views have already been done for each api_root
698
+ ::Brick.relations.each do |k, v|
699
+ if (schema_name = v.fetch(:schema, nil))
700
+ schema_prefix = "#{schema_name}."
701
+ end
702
+
703
+ next if !(resource_name = v.fetch(:resource, nil)) ||
704
+ existing_controllers.key?(
705
+ controller_prefix + (resource_name = "#{schema_prefix&.tr('.', '/')}#{resource_name}".pluralize)
706
+ )
707
+
708
+ object_name = k.split('.').last # Take off any first schema part
709
+
710
+ full_schema_prefix = if (aps = v.fetch(:auto_prefixed_schema, nil))
711
+ aps = aps[0..-2] if aps[-1] == '_'
712
+ (schema_prefix&.dup || +'') << "#{aps}."
713
+ else
714
+ schema_prefix
715
+ end
716
+
717
+ # Track routes being built
718
+ if (class_name = v.fetch(:class_name, nil))
719
+ if v.key?(:isView)
720
+ view_class_length = class_name.length if class_name.length > view_class_length
721
+ views
679
722
  else
680
- send(:resources, res_name.to_sym, **options)
681
- end
723
+ table_class_length = class_name.length if class_name.length > table_class_length
724
+ tables
725
+ end << [class_name, aps, resource_name]
682
726
  end
683
727
 
684
- # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
685
- # If auto-controllers and auto-models are both enabled then this makes sense:
686
- controller_prefix = (path_prefix ? "#{path_prefix}/" : '')
687
- sti_subclasses = ::Brick.config.sti_namespace_prefixes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
688
- # Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
689
- s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
690
- end
691
- versioned_views = {} # Track which views have already been done for each api_root
692
- ::Brick.relations.each do |k, v|
693
- if (schema_name = v.fetch(:schema, nil))
694
- schema_prefix = "#{schema_name}."
695
- end
728
+ options = {}
729
+ options[:only] = [:index, :show] if v.key?(:isView)
730
+
731
+ # First do the normal routes
732
+ prefixes = []
733
+ prefixes << [aps, v[:class_name]&.split('::')[-2]&.underscore] if aps
734
+ prefixes << schema_name if schema_name
735
+ prefixes << path_prefix if path_prefix
736
+ brick_namespace_create.call(prefixes, v[:resource], options)
737
+ sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
738
+ brick_namespace_create.call(prefixes, sc.underscore.tr('/', '_').pluralize, options)
739
+ end
696
740
 
697
- next if !(resource_name = v.fetch(:resource, nil)) ||
698
- existing_controllers.key?(controller_name = (
699
- resource_name = "#{schema_prefix&.tr('.', '/')}#{resource_name}"
700
- ).pluralize)
701
-
702
- object_name = k.split('.').last # Take off any first schema part
703
-
704
- full_schema_prefix = if (aps = v.fetch(:auto_prefixed_schema, nil))
705
- aps = aps[0..-2] if aps[-1] == '_'
706
- (schema_prefix&.dup || +'') << "#{aps}."
707
- else
708
- schema_prefix
709
- end
710
-
711
- # Track routes being built
712
- if (class_name = v.fetch(:class_name, nil))
713
- if v.key?(:isView)
714
- view_class_length = class_name.length if class_name.length > view_class_length
715
- views
741
+ # Now the API routes if necessary
742
+ full_resource = nil
743
+ ::Brick.api_roots&.each do |api_root|
744
+ api_done_views = (versioned_views[api_root] ||= {})
745
+ found = nil
746
+ test_ver_num = nil
747
+ view_relation = nil
748
+ # If it's a view then see if there's a versioned one available by searching for resource names
749
+ # versioned with the closest number (equal to or less than) compared with our API version number.
750
+ if v.key?(:isView)
751
+ if (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
752
+ core_object_name = object_name[ver.length + 1..-1]
753
+ next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
754
+
755
+ # Expect that the last item in the path generally holds versioning information
756
+ api_ver = api_root.split('/')[-1]&.gsub('_', '.')
757
+ vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
758
+ # Was: .to_d
759
+ test_ver_num = api_ver_num = api_ver[vn_idx + 1..-1].gsub('_', '.').to_i # Attempt to turn something like "v3" into the decimal value 3
760
+ # puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
761
+
762
+ next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
763
+
764
+ test_ver_num -= 1 until test_ver_num.zero? ||
765
+ (view_relation = ::Brick.relations.fetch(
766
+ found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
767
+ ))
768
+ api_done_views[unversioned] = nil # Mark that for this API version this view is done
769
+
770
+ # puts "Found #{found}" if view_relation
771
+ # If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
772
+ # fall back to simply looking for "v_view_name", and then finally "view_name".
773
+ no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
774
+ standard_prefix = 'v_'
716
775
  else
717
- table_class_length = class_name.length if class_name.length > table_class_length
718
- tables
719
- end << [class_name, aps, resource_name]
720
- end
721
-
722
- options = {}
723
- options[:only] = [:index, :show] if v.key?(:isView)
724
-
725
- # First do the normal routes
726
- prefixes = []
727
- prefixes << [aps, v[:class_name]&.split('::')[-2]&.underscore] if aps
728
- prefixes << schema_name if schema_name
729
- prefixes << path_prefix if path_prefix
730
- brick_namespace_create.call(prefixes, v[:resource], options)
731
- sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
732
- brick_namespace_create.call(prefixes, sc.underscore.tr('/', '_').pluralize, options)
776
+ core_object_name = object_name
777
+ end
778
+ if (rvp = ::Brick.config.api_remove_view_prefix) && core_object_name.start_with?(rvp)
779
+ core_object_name.slice!(0, rvp.length)
780
+ end
781
+ no_prefix_name = "#{schema_prefix}#{core_object_name}"
782
+ unversioned = "#{schema_prefix}#{standard_prefix}#{::Brick.config.api_add_view_prefix}#{core_object_name}"
783
+ else
784
+ unversioned = k
733
785
  end
734
786
 
735
- # Now the API routes if necessary
736
- full_resource = nil
737
- ::Brick.api_roots&.each do |api_root|
738
- api_done_views = (versioned_views[api_root] ||= {})
739
- found = nil
740
- test_ver_num = nil
741
- view_relation = nil
742
- # If it's a view then see if there's a versioned one available by searching for resource names
743
- # versioned with the closest number (equal to or less than) compared with our API version number.
744
- if v.key?(:isView)
745
- if (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
746
- core_object_name = object_name[ver.length + 1..-1]
747
- next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
748
-
749
- # Expect that the last item in the path generally holds versioning information
750
- api_ver = api_root.split('/')[-1]&.gsub('_', '.')
751
- vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
752
- # Was: .to_d
753
- test_ver_num = api_ver_num = api_ver[vn_idx + 1..-1].gsub('_', '.').to_i # Attempt to turn something like "v3" into the decimal value 3
754
- # puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
755
-
756
- next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
757
-
758
- test_ver_num -= 1 until test_ver_num.zero? ||
759
- (view_relation = ::Brick.relations.fetch(
760
- found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
761
- ))
762
- api_done_views[unversioned] = nil # Mark that for this API version this view is done
763
-
764
- # puts "Found #{found}" if view_relation
765
- # If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
766
- # fall back to simply looking for "v_view_name", and then finally "view_name".
767
- no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
768
- standard_prefix = 'v_'
769
- else
770
- core_object_name = object_name
771
- end
772
- if (rvp = ::Brick.config.api_remove_view_prefix) && core_object_name.start_with?(rvp)
773
- core_object_name.slice!(0, rvp.length)
787
+ view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
788
+ (no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil)) ||
789
+ (no_prefix_name && ::Brick.relations.fetch(found = no_prefix_name, nil))
790
+ if view_relation
791
+ actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
792
+ # Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
793
+ # Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
794
+ # these 3 things controls and changes the nature of the endpoint that gets built:
795
+ # (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
796
+ proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
797
+ begin
798
+ num_args = filter.arity.negative? ? 6 : filter.arity
799
+ filter.call(*[unversioned, k, view_relation, actions, api_ver_num, found, test_ver_num][0...num_args])
800
+ rescue StandardError => e
801
+ puts "::Brick.api_filter Proc error: #{e.message}"
802
+ end
803
+ end
804
+ # proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
805
+
806
+ case proc_result
807
+ when NilClass
808
+ # Do nothing differently than what normal behaviour would be
809
+ when FalseClass # Skip implementing this endpoint
810
+ view_relation[:api][api_ver_num] = nil
811
+ next
812
+ when Array # Did they give back an array of actions?
813
+ unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
814
+ proc_result = [unversioned, to_relation, proc_result]
774
815
  end
775
- no_prefix_name = "#{schema_prefix}#{core_object_name}"
776
- unversioned = "#{schema_prefix}#{standard_prefix}#{::Brick.config.api_add_view_prefix}#{core_object_name}"
816
+ # Otherwise don't change this array because it's probably legit
817
+ when String
818
+ proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
777
819
  else
778
- unversioned = k
820
+ puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n #{proc_result.inspect}"
821
+ proc_result = nil # Couldn't understand what in the world was returned
779
822
  end
780
823
 
781
- view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
782
- (no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil)) ||
783
- (no_prefix_name && ::Brick.relations.fetch(found = no_prefix_name, nil))
784
- if view_relation
785
- actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
786
- # Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
787
- # Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
788
- # these 3 things controls and changes the nature of the endpoint that gets built:
789
- # (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
790
- proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
791
- begin
792
- num_args = filter.arity.negative? ? 6 : filter.arity
793
- filter.call(*[unversioned, k, view_relation, actions, api_ver_num, found, test_ver_num][0...num_args])
794
- rescue StandardError => e
795
- puts "::Brick.api_filter Proc error: #{e.message}"
796
- end
797
- end
798
- # proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
799
-
800
- case proc_result
801
- when NilClass
802
- # Do nothing differently than what normal behaviour would be
803
- when FalseClass # Skip implementing this endpoint
804
- view_relation[:api][api_ver_num] = nil
805
- next
806
- when Array # Did they give back an array of actions?
807
- unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
808
- proc_result = [unversioned, to_relation, proc_result]
824
+ if proc_result&.present?
825
+ if proc_result[1] # to_other_relation
826
+ if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
827
+ k = proc_result[1] # Route this call over to this different relation
828
+ view_relation = new_view_relation
829
+ else
830
+ puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
809
831
  end
810
- # Otherwise don't change this array because it's probably legit
811
- when String
812
- proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
813
- else
814
- puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n #{proc_result.inspect}"
815
- proc_result = nil # Couldn't understand what in the world was returned
816
832
  end
817
-
818
- if proc_result&.present?
819
- if proc_result[1] # to_other_relation
820
- if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
821
- k = proc_result[1] # Route this call over to this different relation
822
- view_relation = new_view_relation
823
- else
824
- puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
825
- end
826
- end
827
- if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
828
- found = proc_result.first
829
- end
830
- actions &= proc_result[2] if proc_result[2] # allowed_actions
833
+ if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
834
+ found = proc_result.first
831
835
  end
832
- (view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
833
-
834
- # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
835
- # first_part[1..-1].gsub('_', '.').to_i
836
- # end
837
- controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
838
- "#{full_schema_prefix}#{last}"
839
- else
840
- found
841
- end.tr('.', '/')
842
-
843
- { :index => 'get', :create => 'post' }.each do |action, method|
844
- if actions.include?(action)
845
- # Normally goes to something like: /api/v1/employees
846
- send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
847
- end
836
+ actions &= proc_result[2] if proc_result[2] # allowed_actions
837
+ end
838
+ (view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
839
+
840
+ # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
841
+ # first_part[1..-1].gsub('_', '.').to_i
842
+ # end
843
+ controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
844
+ "#{full_schema_prefix}#{last}"
845
+ else
846
+ found
847
+ end.tr('.', '/')
848
+
849
+ { :index => 'get', :create => 'post' }.each do |action, method|
850
+ if actions.include?(action)
851
+ # Normally goes to something like: /api/v1/employees
852
+ send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
848
853
  end
849
- # %%% We do not yet surface the #show action
850
- if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
851
- { :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
852
- if actions.include?(action)
853
- methods.each do |method|
854
- send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
855
- end
854
+ end
855
+ # %%% We do not yet surface the #show action
856
+ if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
857
+ { :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
858
+ if actions.include?(action)
859
+ methods.each do |method|
860
+ send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
856
861
  end
857
862
  end
858
863
  end
@@ -860,64 +865,72 @@ In config/initializers/brick.rb appropriate entries would look something like:
860
865
  end
861
866
  end
862
867
 
863
- if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
864
- get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: 'brick_status')
868
+ # Trestle compatibility
869
+ if Object.const_defined?('Trestle') && ::Trestle.config.options&.key?(:site_title) &&
870
+ !Object.const_defined?("#{resource_name.camelize}Admin")
871
+ ::Trestle.resource(res_sym = k.to_sym) do
872
+ menu { item res_sym, icon: "fa fa-star" }
873
+ end
865
874
  end
875
+ end
866
876
 
867
- if ::Brick.config.add_orphans && instance_variable_get(:@set).named_routes.names.exclude?(:brick_orphans)
868
- get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
869
- end
877
+ if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
878
+ get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: 'brick_status')
879
+ end
870
880
 
871
- if instance_variable_get(:@set).named_routes.names.exclude?(:brick_crosstab)
872
- get("/#{controller_prefix}brick_crosstab", to: 'brick_gem#crosstab', as: 'brick_crosstab')
873
- get("/#{controller_prefix}brick_crosstab/data", to: 'brick_gem#crosstab_data')
874
- end
881
+ if ::Brick.config.add_orphans && instance_variable_get(:@set).named_routes.names.exclude?(:brick_orphans)
882
+ get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
883
+ end
875
884
 
876
- unless ::Brick.routes_done
877
- if Object.const_defined?('Rswag::Ui')
878
- rswag_path = ::Rails.application.routes.routes.find { |r| r.app.app == Rswag::Ui::Engine }&.instance_variable_get(:@path_formatter)&.instance_variable_get(:@parts)&.join
879
- first_endpoint_parts = nil
880
- (doc_endpoints = Rswag::Ui.config.config_object[:urls])&.each do |doc_endpoint|
881
- puts "Mounting OpenApi 3.0 documentation endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}"
882
- send(:get, doc_endpoint[:url], { to: 'brick_openapi#index' })
883
- endpoint_parts = doc_endpoint[:url]&.split('/')
884
- first_endpoint_parts ||= endpoint_parts
885
- end
886
- if doc_endpoints.present?
887
- if rswag_path && first_endpoint_parts
888
- puts "API documentation now available when navigating to: /#{first_endpoint_parts&.find(&:present?)}/index.html"
889
- else
890
- puts "In order to make documentation available you can put this into your routes.rb:"
891
- puts " mount Rswag::Ui::Engine => '/#{first_endpoint_parts&.find(&:present?) || 'api-docs'}'"
892
- end
885
+ if instance_variable_get(:@set).named_routes.names.exclude?(:brick_crosstab)
886
+ get("/#{controller_prefix}brick_crosstab", to: 'brick_gem#crosstab', as: 'brick_crosstab')
887
+ get("/#{controller_prefix}brick_crosstab/data", to: 'brick_gem#crosstab_data')
888
+ end
889
+
890
+ unless ::Brick.routes_done
891
+ if Object.const_defined?('Rswag::Ui')
892
+ rswag_path = routeset_to_use.routes.find { |r| r.app.app == Rswag::Ui::Engine }&.instance_variable_get(:@path_formatter)&.instance_variable_get(:@parts)&.join
893
+ first_endpoint_parts = nil
894
+ (doc_endpoints = Rswag::Ui.config.config_object[:urls])&.each do |doc_endpoint|
895
+ puts "Mounting OpenApi 3.0 documentation endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}"
896
+ send(:get, doc_endpoint[:url], { to: 'brick_openapi#index' })
897
+ endpoint_parts = doc_endpoint[:url]&.split('/')
898
+ first_endpoint_parts ||= endpoint_parts
899
+ end
900
+ if doc_endpoints.present?
901
+ if rswag_path && first_endpoint_parts
902
+ puts "API documentation now available when navigating to: /#{first_endpoint_parts&.find(&:present?)}/index.html"
893
903
  else
894
- sample_path = rswag_path || '/api-docs'
904
+ puts "In order to make documentation available you can put this into your routes.rb:"
905
+ puts " mount Rswag::Ui::Engine => '/#{first_endpoint_parts&.find(&:present?) || 'api-docs'}'"
906
+ end
907
+ else
908
+ sample_path = rswag_path || '/api-docs'
909
+ puts
910
+ puts "Brick: rswag-ui gem detected -- to make OpenAPI 3.0 documentation available from a path such as '#{sample_path}/v1/swagger.json',"
911
+ puts ' put code such as this in an initializer:'
912
+ puts ' Rswag::Ui.configure do |config|'
913
+ puts " config.swagger_endpoint '#{sample_path}/v1/swagger.json', 'API V1 Docs'"
914
+ puts ' end'
915
+ unless rswag_path
895
916
  puts
896
- puts "Brick: rswag-ui gem detected -- to make OpenAPI 3.0 documentation available from a path such as '#{sample_path}/v1/swagger.json',"
897
- puts ' put code such as this in an initializer:'
898
- puts ' Rswag::Ui.configure do |config|'
899
- puts " config.swagger_endpoint '#{sample_path}/v1/swagger.json', 'API V1 Docs'"
900
- puts ' end'
901
- unless rswag_path
902
- puts
903
- puts ' and put this into your routes.rb:'
904
- puts " mount Rswag::Ui::Engine => '/api-docs'"
905
- end
917
+ puts ' and put this into your routes.rb:'
918
+ puts " mount Rswag::Ui::Engine => '/api-docs'"
906
919
  end
907
920
  end
921
+ end
908
922
 
909
- ::Brick.routes_done = true
910
- puts "\n" if tables.present? || views.present?
911
- if tables.present?
912
- puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
913
- puts "======================================#{' ' * (table_class_length - 38)} ====="
914
- ::Brick.display_classes(controller_prefix, tables, table_class_length)
915
- end
916
- if views.present?
917
- puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
918
- puts "=====================================#{' ' * (view_class_length - 37)} ====="
919
- ::Brick.display_classes(controller_prefix, views, view_class_length)
920
- end
923
+ ::Brick.routes_done = true
924
+ puts "\n" if tables.present? || views.present?
925
+ if tables.present?
926
+ puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
927
+ puts "======================================#{' ' * (table_class_length - 38)} ====="
928
+ ::Brick.display_classes(controller_prefix, tables, table_class_length)
929
+ end
930
+ if views.present?
931
+ puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
932
+ puts "=====================================#{' ' * (view_class_length - 37)} ====="
933
+ ::Brick.display_classes(controller_prefix, views, view_class_length)
921
934
  end
922
935
  end
923
936
  end
@@ -261,6 +261,10 @@ if ActiveRecord::Base.respond_to?(:brick_select) && !::Brick.initializer_loaded
261
261
  # # Designated by <table name>.<column name>
262
262
  # Brick.not_nullables = ['users.name']
263
263
 
264
+ # # String or text columns which for editing purposes should be treated as JSON. Format for the hash is:
265
+ # # { table_name => [column names] }
266
+ # Brick.json_columns = { 'users' => ['info'] }
267
+
264
268
  # # FRIENDLY DSL
265
269
 
266
270
  # # A simple DSL is available to allow more user-friendly display of objects. Normally a user object might be shown
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.112
4
+ version: 1.0.114
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-14 00:00:00.000000000 Z
11
+ date: 2023-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord