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 +4 -4
- data/lib/brick/config.rb +8 -0
- data/lib/brick/extensions.rb +22 -7
- data/lib/brick/frameworks/rails/engine.rb +73 -8
- data/lib/brick/frameworks/rails/form_tags.rb +3 -2
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +246 -233
- data/lib/generators/brick/install_generator.rb +4 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5006a6dabf5c9e86965e57a40ae344f9d67c2fc5e76caec8a00e978117089062
|
4
|
+
data.tar.gz: 5bd0fef75e305514b4ef22e4930660cdbf9385c22b07ee92d6eb77a626a13943
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/brick/extensions.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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}"
|
data/lib/brick/version_number.rb
CHANGED
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 '
|
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
|
-
|
658
|
-
unless
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
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
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
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
|
-
|
681
|
-
|
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
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
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
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
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
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
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
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
#
|
743
|
-
#
|
744
|
-
if
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
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
|
-
|
776
|
-
|
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
|
-
|
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
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
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
|
-
|
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
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
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
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
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
|
-
|
864
|
-
|
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
|
-
|
868
|
-
|
869
|
-
|
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
|
-
|
872
|
-
|
873
|
-
|
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
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
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
|
-
|
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
|
897
|
-
puts
|
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
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
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.
|
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-
|
11
|
+
date: 2023-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|