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 +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
|