brick 1.0.112 → 1.0.114

Sign up to get free protection for your applications and to get access to all the features.
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