brick 1.0.191 → 1.0.192
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/extensions.rb +65 -20
- data/lib/brick/frameworks/rails/engine.rb +11 -3
- data/lib/brick/frameworks/rails/form_builder.rb +2 -1
- data/lib/brick/frameworks/rails/form_tags.rb +8 -2
- data/lib/brick/frameworks/rails.rb +6 -0
- data/lib/brick/route_mapper.rb +348 -0
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +6 -336
- data/lib/generators/brick/controllers_generator.rb +91 -0
- data/lib/generators/brick/migration_builder.rb +1 -1
- data/lib/generators/brick/models_generator.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 148c8f46702d06322322c04487a1045e6d7a3927611394a84ffe138be78adb5e
|
4
|
+
data.tar.gz: 716bfaae50dc99809bc3aee4eecf5ff1c44325bca54271db225a765cdd97fb56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d97d94ed8d951718b34d1fc0d3ee8d023b3765bafc759fd41161155c927014ddec54a67e371363bbb17b18a614f4be16862d9cb51be313438050a3ce6e3356a2
|
7
|
+
data.tar.gz: efbd8b062eccd5d5441aa8e9fe6162bce076e188761f5c7877f9ca454899fc13873fa6cdf4db21cd2154e24c824487f56a085f41402432bd99486e17213e7f00
|
data/lib/brick/extensions.rb
CHANGED
@@ -82,8 +82,10 @@ module ActiveRecord
|
|
82
82
|
|
83
83
|
def json_column?(col)
|
84
84
|
col.type == :json || ::Brick.config.json_columns[table_name]&.include?(col.name) ||
|
85
|
-
(
|
86
|
-
|
85
|
+
(
|
86
|
+
respond_to?(:attribute_types) && (attr_types = attribute_types[col.name]).respond_to?(:coder) &&
|
87
|
+
(attr_types.coder.is_a?(Class) ? attr_types.coder : attr_types.coder&.class)&.name&.end_with?('JSON')
|
88
|
+
)
|
87
89
|
end
|
88
90
|
|
89
91
|
def brick_foreign_type(assoc)
|
@@ -284,7 +286,10 @@ module ActiveRecord
|
|
284
286
|
end
|
285
287
|
this_obj&.to_s || ''
|
286
288
|
end
|
287
|
-
|
289
|
+
begin
|
290
|
+
is_brackets_have_content = true unless datum.blank?
|
291
|
+
rescue
|
292
|
+
end
|
288
293
|
output << (datum || '')
|
289
294
|
bracket_name = nil
|
290
295
|
else
|
@@ -1357,7 +1362,7 @@ end
|
|
1357
1362
|
end
|
1358
1363
|
rescue NameError # If the const_get for the model has failed...
|
1359
1364
|
skip_controller = true
|
1360
|
-
# ... then just fall through and allow it to fail when trying to
|
1365
|
+
# ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
|
1361
1366
|
end
|
1362
1367
|
end
|
1363
1368
|
unless skip_controller
|
@@ -1742,7 +1747,7 @@ class Object
|
|
1742
1747
|
options[:optional] = true if assoc.key?(:optional)
|
1743
1748
|
if assoc.key?(:polymorphic) ||
|
1744
1749
|
# If a polymorphic association is missing but could be established then go ahead and put it into place.
|
1745
|
-
relations
|
1750
|
+
relations.fetch(assoc[:inverse_table], nil)&.fetch(:class_name, nil)&.constantize&.reflect_on_all_associations&.find { |inv_assoc| !inv_assoc.belongs_to? && inv_assoc.options[:as].to_s == assoc[:assoc_name] }
|
1746
1751
|
assoc[:polymorphic] ||= true
|
1747
1752
|
options[:polymorphic] = true
|
1748
1753
|
else
|
@@ -2645,6 +2650,10 @@ end.class_exec do
|
|
2645
2650
|
orig_schema = nil
|
2646
2651
|
if (relations = ::Brick.relations).keys == [:db_name]
|
2647
2652
|
::Brick.remove_instance_variable(:@_additional_references_loaded) if ::Brick.instance_variable_defined?(:@_additional_references_loaded)
|
2653
|
+
|
2654
|
+
# --------------------------------------------
|
2655
|
+
# 1. Load three initializers early
|
2656
|
+
# (inflectsions.rb, brick.rb, apartment.rb)
|
2648
2657
|
# Very first thing, load inflections since we'll be using .pluralize and .singularize on table and model names
|
2649
2658
|
if File.exist?(inflections = ::Rails.root&.join('config/initializers/inflections.rb') || '')
|
2650
2659
|
load inflections
|
@@ -2695,6 +2704,8 @@ end.class_exec do
|
|
2695
2704
|
# Only for Postgres (Doesn't work in sqlite3 or MySQL)
|
2696
2705
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
2697
2706
|
|
2707
|
+
# ---------------------------
|
2708
|
+
# 2. Figure out schema things
|
2698
2709
|
is_postgres = nil
|
2699
2710
|
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
|
2700
2711
|
case ActiveRecord::Base.connection.adapter_name
|
@@ -2767,6 +2778,8 @@ end.class_exec do
|
|
2767
2778
|
|
2768
2779
|
::Brick.db_schemas ||= {}
|
2769
2780
|
|
2781
|
+
# ---------------------
|
2782
|
+
# 3. Tables and columns
|
2770
2783
|
# %%% Retrieve internal ActiveRecord table names like this:
|
2771
2784
|
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
2772
2785
|
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
@@ -2896,20 +2909,34 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2896
2909
|
# end
|
2897
2910
|
# end
|
2898
2911
|
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
2912
|
+
|
2913
|
+
# ---------------------------------------------
|
2914
|
+
# 4. Foreign key info
|
2915
|
+
# (done in two parts which get JOINed together in Ruby code)
|
2899
2916
|
kcus = nil
|
2917
|
+
entry_type = nil
|
2900
2918
|
case ActiveRecord::Base.connection.adapter_name
|
2901
2919
|
when 'PostgreSQL', 'Mysql2', 'Trilogy', 'SQLServer'
|
2902
|
-
#
|
2920
|
+
# Part 1 -- all KCUs
|
2903
2921
|
sql = "SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, ORDINAL_POSITION,
|
2904
2922
|
TABLE_NAME, COLUMN_NAME
|
2905
2923
|
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE#{"
|
2906
|
-
WHERE CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }"
|
2924
|
+
WHERE CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }#{"
|
2925
|
+
WHERE CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql
|
2926
|
+
}"
|
2907
2927
|
kcus = ActiveRecord::Base.execute_sql(sql).each_with_object({}) do |v, s|
|
2908
|
-
|
2909
|
-
|
2910
|
-
|
2928
|
+
if (entry_type ||= v.is_a?(Array) ? :array : :hash) == :hash
|
2929
|
+
key = "#{v['constraint_name']}.#{v['constraint_schema']}.#{v['constraint_catalog']}.#{v['ordinal_position']}"
|
2930
|
+
key << ".#{v['table_name']}.#{v['column_name']}" unless is_postgres || is_mssql
|
2931
|
+
s[key] = [v['constraint_schema'], v['table_name']]
|
2932
|
+
else # Array
|
2933
|
+
key = "#{v[2]}.#{v[1]}.#{v[0]}.#{v[3]}"
|
2934
|
+
key << ".#{v[4]}.#{v[5]}" unless is_postgres || is_mssql
|
2935
|
+
s[key] = [v[1], v[4]]
|
2936
|
+
end
|
2911
2937
|
end
|
2912
2938
|
|
2939
|
+
# Part 2 -- fk_references
|
2913
2940
|
sql = "SELECT kcu.CONSTRAINT_SCHEMA, kcu.TABLE_NAME, kcu.COLUMN_NAME,
|
2914
2941
|
#{# These will get filled in with real values (effectively doing the JOIN in Ruby)
|
2915
2942
|
is_postgres || is_mssql ? 'NULL as primary_schema, NULL as primary_table' :
|
@@ -2921,7 +2948,8 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2921
2948
|
ON kcu.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
|
2922
2949
|
AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
2923
2950
|
AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME#{"
|
2924
|
-
WHERE kcu.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema
|
2951
|
+
WHERE kcu.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema}#{"
|
2952
|
+
WHERE kcu.CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql}"
|
2925
2953
|
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2926
2954
|
when 'SQLite'
|
2927
2955
|
sql = "SELECT NULL AS constraint_schema, m.name, fkl.\"from\", NULL AS primary_schema, fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
@@ -2952,8 +2980,10 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
2952
2980
|
::Brick.default_schema ||= 'public' if is_postgres
|
2953
2981
|
fk_references&.each do |fk|
|
2954
2982
|
fk = fk.values unless fk.is_a?(Array)
|
2955
|
-
# Virtually JOIN
|
2956
|
-
|
2983
|
+
# Virtually JOIN KCUs to fk_references in order to fill in the primary schema and primary table
|
2984
|
+
kcu_key = "#{fk[6]}.#{fk[7]}.#{fk[8]}.#{fk[9]}"
|
2985
|
+
kcu_key << ".#{fk[3]}.#{fk[4]}" unless is_postgres || is_mssql
|
2986
|
+
if (kcu = kcus&.fetch(kcu_key, nil))
|
2957
2987
|
fk[3] = kcu[0]
|
2958
2988
|
fk[4] = kcu[1]
|
2959
2989
|
end
|
@@ -3044,24 +3074,39 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
3044
3074
|
::Brick.load_additional_references if ::Brick.initializer_loaded
|
3045
3075
|
|
3046
3076
|
if is_postgres
|
3077
|
+
params = []
|
3047
3078
|
ActiveRecord::Base.execute_sql("-- inherited and partitioned tables counts
|
3048
|
-
SELECT parent.relname,
|
3079
|
+
SELECT n.nspname, parent.relname,
|
3049
3080
|
((SUM(child.reltuples::float) / greatest(SUM(child.relpages), 1))) *
|
3050
3081
|
(SUM(pg_relation_size(child.oid))::float / (current_setting('block_size')::float))::integer AS rowcount
|
3051
3082
|
FROM pg_inherits
|
3052
3083
|
INNER JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
3053
3084
|
INNER JOIN pg_class child ON pg_inherits.inhrelid = child.oid
|
3054
|
-
|
3085
|
+
INNER JOIN pg_catalog.pg_namespace n ON n.oid = parent.relnamespace#{
|
3086
|
+
if schema
|
3087
|
+
params = params << schema
|
3088
|
+
"
|
3089
|
+
WHERE n.nspname = COALESCE(?, 'public')"
|
3090
|
+
end}
|
3091
|
+
GROUP BY n.nspname, parent.relname, child.reltuples, child.relpages, child.oid
|
3055
3092
|
|
3056
3093
|
UNION ALL
|
3057
3094
|
|
3058
3095
|
-- table count
|
3059
|
-
SELECT relname,
|
3060
|
-
(reltuples::float / greatest(relpages, 1)) *
|
3096
|
+
SELECT n.nspname, pg_class.relname,
|
3097
|
+
(pg_class.reltuples::float / greatest(pg_class.relpages, 1)) *
|
3061
3098
|
(pg_relation_size(pg_class.oid)::float / (current_setting('block_size')::float))::integer AS rowcount
|
3062
3099
|
FROM pg_class
|
3063
|
-
|
3064
|
-
|
3100
|
+
INNER JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace#{
|
3101
|
+
if schema
|
3102
|
+
params = params << schema
|
3103
|
+
"
|
3104
|
+
WHERE n.nspname = COALESCE(?, 'public')"
|
3105
|
+
end}
|
3106
|
+
GROUP BY n.nspname, pg_class.relname, pg_class.reltuples, pg_class.relpages, pg_class.oid", params).each do |tblcount|
|
3107
|
+
# %%% What is the default schema here?
|
3108
|
+
prefix = "#{tblcount['nspname']}." unless tblcount['nspname'] == (schema || 'public')
|
3109
|
+
relations.fetch("#{prefix}#{tblcount['relname']}", nil)&.[]=(:rowcount, tblcount['rowcount'].to_i.round)
|
3065
3110
|
end
|
3066
3111
|
end
|
3067
3112
|
|
@@ -3430,7 +3475,7 @@ module Brick
|
|
3430
3475
|
separator ||= '_'
|
3431
3476
|
res_name = (tbl_name_parts = tbl_name.split('.'))[0..-2].first
|
3432
3477
|
res_name << '.' if res_name
|
3433
|
-
(res_name ||= +'') << (relation || ::Brick.relations.fetch(tbl_name, nil)&.fetch(:resource, nil) || tbl_name_parts.last
|
3478
|
+
(res_name ||= +'') << (relation || ::Brick.relations.fetch(tbl_name, nil))&.fetch(:resource, nil) || tbl_name_parts.last
|
3434
3479
|
|
3435
3480
|
res_parts = ((mode == :singular) ? res_name.singularize : res_name).split('.')
|
3436
3481
|
res_parts.shift if ::Brick.apartment_multitenant && res_parts.length > 1 && res_parts.first == ::Brick.apartment_default_tenant
|
@@ -345,7 +345,8 @@ function linkSchemas() {
|
|
345
345
|
end
|
346
346
|
::Brick.relations.each do |k, v|
|
347
347
|
unless k.is_a?(Symbol) || existing.key?(class_name = v[:class_name]) || Brick.config.exclude_tables.include?(k) ||
|
348
|
-
class_name.blank? || class_name.include?('::')
|
348
|
+
class_name.blank? || class_name.include?('::') ||
|
349
|
+
['ActiveAdminComment', 'MotorAlert', 'MotorAlertLock', 'MotorApiConfig', 'MotorAudit', 'MotorConfig', 'MotorDashboard', 'MotorForm', 'MotorNote', 'MotorNoteTag', 'MotorNoteTagTag', 'MotorNotification', 'MotorQuery', 'MotorReminder', 'MotorResource', 'MotorTag', 'MotorTaggableTag'].include?(class_name)
|
349
350
|
Object.const_get("#{class_name}Resource")
|
350
351
|
end
|
351
352
|
end
|
@@ -1583,7 +1584,13 @@ end %>#{"
|
|
1583
1584
|
#{schema_options}" if schema_options}
|
1584
1585
|
<select id=\"tbl\">#{table_options}</select>
|
1585
1586
|
<table id=\"resourceName\"><td><h1><%= page_title %></h1></td>
|
1586
|
-
<%
|
1587
|
+
<% rel = Brick.relations[#{model_name}.table_name]
|
1588
|
+
if (in_app = rel.fetch(:existing, nil)&.fetch(:show, nil))
|
1589
|
+
in_app = send(\"#\{in_app}_path\", #{pk.is_a?(String) ? "obj.#{pk}" : '[' + pk.map { |pk_part| "obj.#{pk_part}" }.join(', ') + ']' }) if in_app.is_a?(Symbol) %>
|
1590
|
+
<td><%= link_to(::Brick::Rails::IN_APP.html_safe, in_app) %></td>
|
1591
|
+
<% end
|
1592
|
+
|
1593
|
+
if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) %>
|
1587
1594
|
<td><%= link_to_brick(
|
1588
1595
|
::Brick::Rails::AVO_SVG.html_safe,
|
1589
1596
|
{ show_proc: Proc.new do |obj, relation|
|
@@ -1608,7 +1615,7 @@ end %>#{"
|
|
1608
1615
|
end %>
|
1609
1616
|
</table>
|
1610
1617
|
<%
|
1611
|
-
if (description =
|
1618
|
+
if (description = rel&.fetch(:description, nil)) %>
|
1612
1619
|
<span class=\"__brick\"><%= description %></span><br><%
|
1613
1620
|
end
|
1614
1621
|
%><%= link_to \"(See all #\{model_name.pluralize})\", see_all_path, { class: '__brick' } %>
|
@@ -1939,6 +1946,7 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
|
|
1939
1946
|
end
|
1940
1947
|
|
1941
1948
|
if ::Brick.enable_routes?
|
1949
|
+
require 'brick/route_mapper'
|
1942
1950
|
ActionDispatch::Routing::RouteSet.class_exec do
|
1943
1951
|
# In order to defer auto-creation of any routes that already exist, calculate Brick routes only after having loaded all others
|
1944
1952
|
prepend ::Brick::RouteSet
|
@@ -148,7 +148,8 @@ module Brick::Rails::FormBuilder
|
|
148
148
|
'(hidden)'
|
149
149
|
else
|
150
150
|
if val.is_a?(String)
|
151
|
-
val = val.dup.force_encoding('UTF-8').
|
151
|
+
return ::Brick::Rails.display_binary(val) unless (val_utf8 = val.dup.force_encoding('UTF-8')).valid_encoding?
|
152
|
+
val = val_utf8.strip
|
152
153
|
return CGI.escapeHTML(val) if is_xml
|
153
154
|
|
154
155
|
if val.length > max_len
|
@@ -2,7 +2,7 @@ module Brick::Rails::FormTags
|
|
2
2
|
# Our super speedy grid
|
3
3
|
def brick_grid(relation = nil, sequence = nil, inclusions = nil, exclusions = nil,
|
4
4
|
cols = {}, bt_descrip: nil, poly_cols: nil, bts: {}, hms_keys: [], hms_cols: {},
|
5
|
-
show_header: nil, show_row_count: nil, show_erd_button: nil, show_new_button: nil, show_avo_button: nil, show_aa_button: nil)
|
5
|
+
show_header: nil, show_row_count: nil, show_erd_button: nil, show_in_app_button: nil, show_new_button: nil, show_avo_button: nil, show_aa_button: nil)
|
6
6
|
# When a relation is not provided, first see if one exists which matches the controller name
|
7
7
|
unless (relation ||= instance_variable_get("@#{controller_name}".to_sym))
|
8
8
|
# Failing that, dig through the instance variables with hopes to find something that is an ActiveRecord::Relation
|
@@ -33,6 +33,7 @@ module Brick::Rails::FormTags
|
|
33
33
|
out = +"<div id=\"headerTopContainer\"><table id=\"headerTop\"></table>
|
34
34
|
"
|
35
35
|
klass = relation.klass
|
36
|
+
rel = ::Brick.relations&.fetch(relation.table_name, nil)
|
36
37
|
unless show_header == false
|
37
38
|
out << " <div id=\"headerTopAddNew\">
|
38
39
|
<div id=\"headerButtonBox\">
|
@@ -43,6 +44,11 @@ module Brick::Rails::FormTags
|
|
43
44
|
end
|
44
45
|
unless show_erd_button == false
|
45
46
|
out << " <div id=\"imgErd\" title=\"Show ERD\"></div>
|
47
|
+
"
|
48
|
+
end
|
49
|
+
if rel && show_in_app_button != false && (in_app = rel.fetch(:existing, nil)&.fetch(:index, nil))
|
50
|
+
in_app = send("#{in_app}_path") if in_app.is_a?(Symbol)
|
51
|
+
out << " <td title=\"Show in app\">#{link_to(::Brick::Rails::IN_APP.html_safe, in_app)}</td>
|
46
52
|
"
|
47
53
|
end
|
48
54
|
if show_avo_button != false && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && klass.name.exclude?('::')
|
@@ -254,7 +260,7 @@ module Brick::Rails::FormTags
|
|
254
260
|
out << '</tr>'
|
255
261
|
row_count += 1
|
256
262
|
end
|
257
|
-
if (total_row_count =
|
263
|
+
if rel && (total_row_count = rel.fetch(:rowcount, nil))
|
258
264
|
total_row_count = total_row_count > row_count ? " (out of #{total_row_count})" : nil
|
259
265
|
end
|
260
266
|
out << " </tbody>
|
@@ -15,4 +15,10 @@ module ::Brick::Rails
|
|
15
15
|
|
16
16
|
AA_PNG = "<img src=\"data:image/png;base64,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\">
|
17
17
|
"
|
18
|
+
|
19
|
+
IN_APP = "<svg height=\"36px\" viewBox=\"0 0 214 274\" xmlns=\"http://www.w3.org/2000/svg\">
|
20
|
+
<g transform=\"matrix(4.16667,0,0,4.16667,-1049.47,-789.371)\">
|
21
|
+
<path d=\"M281.386,193.134L287.086,193.134C287.433,193.134 287.716,193.417 287.716,193.764C287.716,194.11 287.433,194.394 287.086,194.394L281.386,194.394C281.04,194.394 280.756,194.11 280.756,193.764C280.756,193.417 281.04,193.134 281.386,193.134ZM284.252,245.638C282.456,245.638 281.008,247.086 281.008,248.851C281.008,250.645 282.456,252.094 284.252,252.094C286.047,252.094 287.496,250.645 287.496,248.851C287.496,247.086 286.047,245.638 284.252,245.638ZM284.252,246.331C282.835,246.331 281.701,247.465 281.701,248.851C281.701,250.268 282.835,251.401 284.252,251.401C285.638,251.401 286.803,250.268 286.803,248.851C286.803,247.465 285.638,246.331 284.252,246.331ZM275.843,208.63L287.559,218.11L275.843,227.622L275.843,221.858L251.874,221.858L251.874,214.394L275.843,214.394L275.843,208.63ZM278.866,239.906L278.866,233.102C278.866,232.851 278.677,232.693 278.456,232.693L271.622,232.693C271.401,232.693 271.212,232.851 271.212,233.102L271.212,239.906C271.212,240.157 271.401,240.314 271.622,240.314L278.456,240.314C278.677,240.314 278.866,240.157 278.866,239.906ZM280.41,233.102L280.41,239.906C280.41,240.157 280.599,240.314 280.819,240.314L287.653,240.314C287.874,240.314 288.063,240.157 288.063,239.906L288.063,233.102C288.063,232.851 287.874,232.693 287.653,232.693L280.819,232.693C280.599,232.693 280.41,232.851 280.41,233.102ZM289.606,233.102L289.606,239.906C289.606,240.157 289.795,240.314 290.047,240.314L296.851,240.314C297.071,240.314 297.26,240.157 297.26,239.906L297.26,233.102C297.26,232.851 297.071,232.693 296.851,232.693L290.047,232.693C289.795,232.693 289.606,232.851 289.606,233.102ZM269.197,189.449L299.276,189.449C301.354,189.449 303.055,191.149 303.055,193.197L303.055,251.275C303.055,253.355 301.354,255.055 299.276,255.055L269.197,255.055C267.118,255.055 265.449,253.355 265.449,251.275L265.449,223.496L267.842,223.496L267.842,242.488L300.63,242.488L300.63,198.394L267.842,198.394L267.842,212.725L265.449,212.725L265.449,193.197C265.449,191.149 267.118,189.449 269.197,189.449Z\" style=\"fill:rgb(31,147,209);\"/>
|
22
|
+
</g>
|
23
|
+
</svg>"
|
18
24
|
end
|
@@ -0,0 +1,348 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Brick
|
4
|
+
class << self
|
5
|
+
attr_accessor :routes_done
|
6
|
+
end
|
7
|
+
|
8
|
+
module RouteMapper
|
9
|
+
def add_brick_routes
|
10
|
+
routeset_to_use = ::Rails.application.routes
|
11
|
+
path_prefix = ::Brick.config.path_prefix
|
12
|
+
existing_controllers = routeset_to_use.routes.each_with_object({}) do |r, s|
|
13
|
+
if (r.verb == 'GET' || (r.verb.is_a?(Regexp) && r.verb.source == '^GET$')) &&
|
14
|
+
(controller_name = r.defaults[:controller])
|
15
|
+
path = r.path.ast.to_s
|
16
|
+
path = path[0..((path.index('(') || 0) - 1)]
|
17
|
+
# Skip adding this if it's the default_route_fallback set from the initializers/brick.rb file
|
18
|
+
next if "#{path}##{r.defaults[:action]}" == ::Brick.established_drf ||
|
19
|
+
# or not a GET request
|
20
|
+
[:index, :show, :new, :edit].exclude?(action = r.defaults[:action].to_sym)
|
21
|
+
|
22
|
+
# Attempt to backtrack to original
|
23
|
+
c_parts = controller_name.split('/')
|
24
|
+
while c_parts.length > 0
|
25
|
+
c_dotted = c_parts.join('.')
|
26
|
+
if (relation = ::Brick.relations.fetch(c_dotted, nil)) # Does it match up with an existing Brick table / resource name?
|
27
|
+
# puts path
|
28
|
+
# puts " #{c_dotted}##{r.defaults[:action]}"
|
29
|
+
if (route_name = r.name&.to_sym) != :root
|
30
|
+
relation[:existing][action] = route_name
|
31
|
+
else
|
32
|
+
relation[:existing][action] ||= path
|
33
|
+
end
|
34
|
+
s[c_dotted.tr('.', '/')] = nil
|
35
|
+
break
|
36
|
+
end
|
37
|
+
c_parts.shift
|
38
|
+
end
|
39
|
+
s[controller_name] = nil if c_parts.length.zero?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
tables = []
|
44
|
+
views = []
|
45
|
+
table_class_length = 38 # Length of "Classes that can be built from tables:"
|
46
|
+
view_class_length = 37 # Length of "Classes that can be built from views:"
|
47
|
+
|
48
|
+
brick_namespace_create = lambda do |path_names, res_name, options|
|
49
|
+
if path_names&.present?
|
50
|
+
if (path_name = path_names.pop).is_a?(Array)
|
51
|
+
module_name = path_name[1]
|
52
|
+
path_name = path_name.first
|
53
|
+
end
|
54
|
+
send(:scope, { module: module_name || path_name, path: path_name, as: path_name }) do
|
55
|
+
brick_namespace_create.call(path_names, res_name, options)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
send(:resources, res_name.to_sym, **options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
63
|
+
# If auto-controllers and auto-models are both enabled then this makes sense:
|
64
|
+
controller_prefix = (path_prefix ? "#{path_prefix}/" : '')
|
65
|
+
sti_subclasses = ::Brick.config.sti_namespace_prefixes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
|
66
|
+
# Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
|
67
|
+
s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
|
68
|
+
end
|
69
|
+
versioned_views = {} # Track which views have already been done for each api_root
|
70
|
+
::Brick.relations.each do |k, v|
|
71
|
+
next if k.is_a?(Symbol)
|
72
|
+
|
73
|
+
if (schema_name = v.fetch(:schema, nil))
|
74
|
+
schema_prefix = "#{schema_name}."
|
75
|
+
end
|
76
|
+
|
77
|
+
resource_name = v.fetch(:resource, nil)
|
78
|
+
next if !resource_name ||
|
79
|
+
existing_controllers.key?(
|
80
|
+
controller_prefix + (resource_name = "#{schema_prefix&.tr('.', '/')}#{resource_name}".pluralize)
|
81
|
+
)
|
82
|
+
|
83
|
+
object_name = k.split('.').last # Take off any first schema part
|
84
|
+
|
85
|
+
full_schema_prefix = if (full_aps = aps = v.fetch(:auto_prefixed_schema, nil))
|
86
|
+
aps = aps[0..-2] if aps[-1] == '_'
|
87
|
+
(schema_prefix&.dup || +'') << "#{aps}."
|
88
|
+
else
|
89
|
+
schema_prefix
|
90
|
+
end
|
91
|
+
|
92
|
+
# Track routes being built
|
93
|
+
resource_name = v.fetch(:resource, nil) || k
|
94
|
+
if (class_name = v.fetch(:class_name, nil))
|
95
|
+
if v.key?(:isView)
|
96
|
+
view_class_length = class_name.length if class_name.length > view_class_length
|
97
|
+
views
|
98
|
+
else
|
99
|
+
table_class_length = class_name.length if class_name.length > table_class_length
|
100
|
+
tables
|
101
|
+
end << [class_name, aps, "#{"#{schema_name}/" if schema_name}#{resource_name[full_aps&.length || 0 .. -1]}"]
|
102
|
+
end
|
103
|
+
|
104
|
+
options = {}
|
105
|
+
options[:only] = [:index, :show] if v.key?(:isView)
|
106
|
+
|
107
|
+
# First do the normal routes
|
108
|
+
prefixes = []
|
109
|
+
prefixes << [aps, v[:class_name]&.split('::')[-2]&.underscore] if aps
|
110
|
+
prefixes << schema_name if schema_name
|
111
|
+
prefixes << path_prefix if path_prefix
|
112
|
+
brick_namespace_create.call(prefixes, resource_name, options)
|
113
|
+
sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
|
114
|
+
brick_namespace_create.call(prefixes, sc.underscore.tr('/', '_').pluralize, options)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Now the API routes if necessary
|
118
|
+
full_resource = nil
|
119
|
+
::Brick.api_roots&.each do |api_root|
|
120
|
+
api_done_views = (versioned_views[api_root] ||= {})
|
121
|
+
found = nil
|
122
|
+
test_ver_num = nil
|
123
|
+
view_relation = nil
|
124
|
+
# If it's a view then see if there's a versioned one available by searching for resource names
|
125
|
+
# versioned with the closest number (equal to or less than) compared with our API version number.
|
126
|
+
if v.key?(:isView)
|
127
|
+
if (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
|
128
|
+
core_object_name = object_name[ver.length + 1..-1]
|
129
|
+
next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
|
130
|
+
|
131
|
+
# Expect that the last item in the path generally holds versioning information
|
132
|
+
api_ver = api_root.split('/')[-1]&.gsub('_', '.')
|
133
|
+
vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
|
134
|
+
# Was: .to_d
|
135
|
+
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
|
136
|
+
# puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
|
137
|
+
|
138
|
+
next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
|
139
|
+
|
140
|
+
test_ver_num -= 1 until test_ver_num.zero? ||
|
141
|
+
(view_relation = ::Brick.relations.fetch(
|
142
|
+
found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
|
143
|
+
))
|
144
|
+
api_done_views[unversioned] = nil # Mark that for this API version this view is done
|
145
|
+
|
146
|
+
# puts "Found #{found}" if view_relation
|
147
|
+
# If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
|
148
|
+
# fall back to simply looking for "v_view_name", and then finally "view_name".
|
149
|
+
no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
|
150
|
+
standard_prefix = 'v_'
|
151
|
+
else
|
152
|
+
core_object_name = object_name
|
153
|
+
end
|
154
|
+
if (rvp = ::Brick.config.api_remove_view_prefix) && core_object_name.start_with?(rvp)
|
155
|
+
core_object_name.slice!(0, rvp.length)
|
156
|
+
end
|
157
|
+
no_prefix_name = "#{schema_prefix}#{core_object_name}"
|
158
|
+
unversioned = "#{schema_prefix}#{standard_prefix}#{::Brick.config.api_add_view_prefix}#{core_object_name}"
|
159
|
+
else
|
160
|
+
unversioned = k
|
161
|
+
end
|
162
|
+
|
163
|
+
view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
|
164
|
+
(no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil)) ||
|
165
|
+
(no_prefix_name && ::Brick.relations.fetch(found = no_prefix_name, nil))
|
166
|
+
if view_relation
|
167
|
+
actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
|
168
|
+
# Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
|
169
|
+
# Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
|
170
|
+
# these 3 things controls and changes the nature of the endpoint that gets built:
|
171
|
+
# (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
|
172
|
+
proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
|
173
|
+
begin
|
174
|
+
num_args = filter.arity.negative? ? 6 : filter.arity
|
175
|
+
filter.call(*[unversioned, k, view_relation, actions, api_ver_num, found, test_ver_num][0...num_args])
|
176
|
+
rescue StandardError => e
|
177
|
+
puts "::Brick.api_filter Proc error: #{e.message}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
# proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
|
181
|
+
|
182
|
+
case proc_result
|
183
|
+
when NilClass
|
184
|
+
# Do nothing differently than what normal behaviour would be
|
185
|
+
when FalseClass # Skip implementing this endpoint
|
186
|
+
view_relation[:api][api_ver_num] = nil
|
187
|
+
next
|
188
|
+
when Array # Did they give back an array of actions?
|
189
|
+
unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
|
190
|
+
proc_result = [unversioned, to_relation, proc_result]
|
191
|
+
end
|
192
|
+
# Otherwise don't change this array because it's probably legit
|
193
|
+
when String
|
194
|
+
proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
|
195
|
+
else
|
196
|
+
puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n #{proc_result.inspect}"
|
197
|
+
proc_result = nil # Couldn't understand what in the world was returned
|
198
|
+
end
|
199
|
+
|
200
|
+
if proc_result&.present?
|
201
|
+
if proc_result[1] # to_other_relation
|
202
|
+
if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
|
203
|
+
k = proc_result[1] # Route this call over to this different relation
|
204
|
+
view_relation = new_view_relation
|
205
|
+
else
|
206
|
+
puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
|
207
|
+
end
|
208
|
+
end
|
209
|
+
if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
|
210
|
+
found = proc_result.first
|
211
|
+
end
|
212
|
+
actions &= proc_result[2] if proc_result[2] # allowed_actions
|
213
|
+
end
|
214
|
+
(view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
|
215
|
+
|
216
|
+
# view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
|
217
|
+
# first_part[1..-1].gsub('_', '.').to_i
|
218
|
+
# end
|
219
|
+
controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
|
220
|
+
"#{full_schema_prefix}#{last}"
|
221
|
+
else
|
222
|
+
found
|
223
|
+
end.tr('.', '/')
|
224
|
+
|
225
|
+
{ :index => 'get', :create => 'post' }.each do |action, method|
|
226
|
+
if actions.include?(action)
|
227
|
+
# Normally goes to something like: /api/v1/employees
|
228
|
+
send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
|
229
|
+
end
|
230
|
+
end
|
231
|
+
# %%% We do not yet surface the #show action
|
232
|
+
if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
|
233
|
+
{ :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
|
234
|
+
if actions.include?(action)
|
235
|
+
methods.each do |method|
|
236
|
+
send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Trestle compatibility
|
245
|
+
if Object.const_defined?('Trestle') && ::Trestle.config.options&.key?(:site_title) &&
|
246
|
+
!Object.const_defined?("#{(res_name = resource_name.tr('/', '_')).camelize}Admin")
|
247
|
+
begin
|
248
|
+
::Trestle.resource(res_sym = res_name.to_sym, model: class_name&.constantize) do
|
249
|
+
menu { item res_sym, icon: "fa fa-star" }
|
250
|
+
end
|
251
|
+
rescue
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
if (named_routes = instance_variable_get(:@set).named_routes).respond_to?(:find)
|
257
|
+
if ::Brick.config.add_status && (status_as = "#{controller_prefix.tr('/', '_')}brick_status".to_sym)
|
258
|
+
(
|
259
|
+
!(status_route = instance_variable_get(:@set).named_routes.find { |route| route.first == status_as }&.last) ||
|
260
|
+
!status_route.ast.to_s.include?("/#{controller_prefix}brick_status/")
|
261
|
+
)
|
262
|
+
get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: status_as.to_s)
|
263
|
+
end
|
264
|
+
|
265
|
+
# ::Brick.config.add_schema &&
|
266
|
+
if (schema_as = "#{controller_prefix.tr('/', '_')}brick_schema".to_sym)
|
267
|
+
(
|
268
|
+
!(schema_route = instance_variable_get(:@set).named_routes.find { |route| route.first == schema_as }&.last) ||
|
269
|
+
!schema_route.ast.to_s.include?("/#{controller_prefix}brick_schema/")
|
270
|
+
)
|
271
|
+
post("/#{controller_prefix}brick_schema", to: 'brick_gem#schema_create', as: schema_as.to_s)
|
272
|
+
end
|
273
|
+
|
274
|
+
if ::Brick.config.add_orphans && (orphans_as = "#{controller_prefix.tr('/', '_')}brick_orphans".to_sym)
|
275
|
+
(
|
276
|
+
!(orphans_route = instance_variable_get(:@set).named_routes.find { |route| route.first == orphans_as }&.last) ||
|
277
|
+
!orphans_route.ast.to_s.include?("/#{controller_prefix}brick_orphans/")
|
278
|
+
)
|
279
|
+
get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
if instance_variable_get(:@set).named_routes.names.exclude?(:brick_crosstab)
|
284
|
+
get("/#{controller_prefix}brick_crosstab", to: 'brick_gem#crosstab', as: 'brick_crosstab')
|
285
|
+
get("/#{controller_prefix}brick_crosstab/data", to: 'brick_gem#crosstab_data')
|
286
|
+
end
|
287
|
+
|
288
|
+
if ((rswag_ui_present = Object.const_defined?('Rswag::Ui')) &&
|
289
|
+
(rswag_path = routeset_to_use.routes.find { |r| r.app.app == ::Rswag::Ui::Engine }
|
290
|
+
&.instance_variable_get(:@path_formatter)
|
291
|
+
&.instance_variable_get(:@parts)&.join) &&
|
292
|
+
(doc_endpoints = ::Rswag::Ui.config.config_object[:urls])) ||
|
293
|
+
(doc_endpoints = ::Brick.instance_variable_get(:@swagger_endpoints))
|
294
|
+
last_endpoint_parts = nil
|
295
|
+
doc_endpoints.each do |doc_endpoint|
|
296
|
+
puts "Mounting OpenApi 3.0 documentation endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}" unless ::Brick.routes_done
|
297
|
+
send(:get, doc_endpoint[:url], { to: 'brick_openapi#index' })
|
298
|
+
endpoint_parts = doc_endpoint[:url]&.split('/')
|
299
|
+
last_endpoint_parts = endpoint_parts
|
300
|
+
end
|
301
|
+
end
|
302
|
+
return if ::Brick.routes_done
|
303
|
+
|
304
|
+
if doc_endpoints.present?
|
305
|
+
if rswag_ui_present
|
306
|
+
if rswag_path
|
307
|
+
puts "API documentation now available when navigating to: /#{last_endpoint_parts&.find(&:present?)}/index.html"
|
308
|
+
else
|
309
|
+
puts "In order to make documentation available you can put this into your routes.rb:"
|
310
|
+
puts " mount Rswag::Ui::Engine => '/#{last_endpoint_parts&.find(&:present?) || 'api-docs'}'"
|
311
|
+
end
|
312
|
+
else
|
313
|
+
puts "Having this exposed, one easy way to leverage this to create HTML-based API documentation is to use Scalar.
|
314
|
+
It will jump to life when you put these two lines into a view template or other HTML resource:
|
315
|
+
<script id=\"api-reference\" data-url=\"#{last_endpoint_parts.join('/')}\"></script>
|
316
|
+
<script src=\"https://cdn.jsdelivr.net/@scalar/api-reference\"></script>
|
317
|
+
Alternatively you can add the rswag-ui gem."
|
318
|
+
end
|
319
|
+
elsif rswag_ui_present
|
320
|
+
sample_path = rswag_path || '/api-docs'
|
321
|
+
puts
|
322
|
+
puts "Brick: rswag-ui gem detected -- to make OpenAPI 3.0 documentation available from a path such as '#{sample_path}/v1/swagger.json',"
|
323
|
+
puts ' put code such as this in an initializer:'
|
324
|
+
puts ' Rswag::Ui.configure do |config|'
|
325
|
+
puts " config.swagger_endpoint '#{sample_path}/v1/swagger.json', 'API V1 Docs'"
|
326
|
+
puts ' end'
|
327
|
+
unless rswag_path
|
328
|
+
puts
|
329
|
+
puts ' and put this into your routes.rb:'
|
330
|
+
puts " mount Rswag::Ui::Engine => '/api-docs'"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
puts "\n" if tables.present? || views.present?
|
335
|
+
if tables.present?
|
336
|
+
puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
|
337
|
+
puts "======================================#{' ' * (table_class_length - 38)} ====="
|
338
|
+
::Brick.display_classes(controller_prefix, tables, table_class_length)
|
339
|
+
end
|
340
|
+
if views.present?
|
341
|
+
puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
|
342
|
+
puts "=====================================#{' ' * (view_class_length - 37)} ====="
|
343
|
+
::Brick.display_classes(controller_prefix, views, view_class_length)
|
344
|
+
end
|
345
|
+
::Brick.routes_done = true
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -107,7 +107,7 @@ module Brick
|
|
107
107
|
end
|
108
108
|
|
109
109
|
attr_accessor :default_schema, :db_schemas, :test_schema,
|
110
|
-
:
|
110
|
+
:established_drf,
|
111
111
|
:is_oracle, :is_eager_loading, :auto_models, :initializer_loaded,
|
112
112
|
:table_name_lookup
|
113
113
|
::Brick.auto_models = []
|
@@ -881,340 +881,6 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
881
881
|
end
|
882
882
|
end
|
883
883
|
|
884
|
-
module RouteMapper
|
885
|
-
def add_brick_routes
|
886
|
-
routeset_to_use = ::Rails.application.routes
|
887
|
-
path_prefix = ::Brick.config.path_prefix
|
888
|
-
existing_controllers = routeset_to_use.routes.each_with_object({}) do |r, s|
|
889
|
-
if r.verb == 'GET' && (c = r.defaults[:controller])
|
890
|
-
path = r.path.ast.to_s
|
891
|
-
path = path[0..((path.index('(') || 0) - 1)]
|
892
|
-
# Skip adding this if it's the default_route_fallback set from the initializers/brick.rb file
|
893
|
-
next if "#{path}##{r.defaults[:action]}" == ::Brick.established_drf
|
894
|
-
|
895
|
-
# next unless [:index, :show, :new, :edit].incude?(a = r.defaults[:action])
|
896
|
-
|
897
|
-
# c_parts = c.split('/')
|
898
|
-
# while c_parts.length > 0
|
899
|
-
# c_dotted = c_parts.join('.')
|
900
|
-
# if (relation = ::Brick.relations[c_dotted]) # Does it match up with an existing Brick table / resource name?
|
901
|
-
# puts path
|
902
|
-
# puts " #{c_dotted}##{r.defaults[:action]}"
|
903
|
-
# s[c_dotted.tr('.', '/')] = nil
|
904
|
-
# break
|
905
|
-
# end
|
906
|
-
# c_parts.shift
|
907
|
-
# end
|
908
|
-
s[c] = nil
|
909
|
-
end
|
910
|
-
end
|
911
|
-
|
912
|
-
tables = []
|
913
|
-
views = []
|
914
|
-
table_class_length = 38 # Length of "Classes that can be built from tables:"
|
915
|
-
view_class_length = 37 # Length of "Classes that can be built from views:"
|
916
|
-
|
917
|
-
brick_namespace_create = lambda do |path_names, res_name, options|
|
918
|
-
if path_names&.present?
|
919
|
-
if (path_name = path_names.pop).is_a?(Array)
|
920
|
-
module_name = path_name[1]
|
921
|
-
path_name = path_name.first
|
922
|
-
end
|
923
|
-
send(:scope, { module: module_name || path_name, path: path_name, as: path_name }) do
|
924
|
-
brick_namespace_create.call(path_names, res_name, options)
|
925
|
-
end
|
926
|
-
else
|
927
|
-
send(:resources, res_name.to_sym, **options)
|
928
|
-
end
|
929
|
-
end
|
930
|
-
|
931
|
-
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
932
|
-
# If auto-controllers and auto-models are both enabled then this makes sense:
|
933
|
-
controller_prefix = (path_prefix ? "#{path_prefix}/" : '')
|
934
|
-
sti_subclasses = ::Brick.config.sti_namespace_prefixes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
|
935
|
-
# Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
|
936
|
-
s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
|
937
|
-
end
|
938
|
-
versioned_views = {} # Track which views have already been done for each api_root
|
939
|
-
::Brick.relations.each do |k, v|
|
940
|
-
next if k.is_a?(Symbol)
|
941
|
-
|
942
|
-
if (schema_name = v.fetch(:schema, nil))
|
943
|
-
schema_prefix = "#{schema_name}."
|
944
|
-
end
|
945
|
-
|
946
|
-
resource_name = v.fetch(:resource, nil)
|
947
|
-
next if !resource_name ||
|
948
|
-
existing_controllers.key?(
|
949
|
-
controller_prefix + (resource_name = "#{schema_prefix&.tr('.', '/')}#{resource_name}".pluralize)
|
950
|
-
)
|
951
|
-
|
952
|
-
object_name = k.split('.').last # Take off any first schema part
|
953
|
-
|
954
|
-
full_schema_prefix = if (full_aps = aps = v.fetch(:auto_prefixed_schema, nil))
|
955
|
-
aps = aps[0..-2] if aps[-1] == '_'
|
956
|
-
(schema_prefix&.dup || +'') << "#{aps}."
|
957
|
-
else
|
958
|
-
schema_prefix
|
959
|
-
end
|
960
|
-
|
961
|
-
# Track routes being built
|
962
|
-
resource_name = v.fetch(:resource, nil) || k
|
963
|
-
if (class_name = v.fetch(:class_name, nil))
|
964
|
-
if v.key?(:isView)
|
965
|
-
view_class_length = class_name.length if class_name.length > view_class_length
|
966
|
-
views
|
967
|
-
else
|
968
|
-
table_class_length = class_name.length if class_name.length > table_class_length
|
969
|
-
tables
|
970
|
-
end << [class_name, aps, resource_name.tr('.', '/')[full_aps&.length || 0 .. -1]]
|
971
|
-
end
|
972
|
-
|
973
|
-
options = {}
|
974
|
-
options[:only] = [:index, :show] if v.key?(:isView)
|
975
|
-
|
976
|
-
# First do the normal routes
|
977
|
-
prefixes = []
|
978
|
-
prefixes << [aps, v[:class_name]&.split('::')[-2]&.underscore] if aps
|
979
|
-
prefixes << schema_name if schema_name
|
980
|
-
prefixes << path_prefix if path_prefix
|
981
|
-
brick_namespace_create.call(prefixes, resource_name, options)
|
982
|
-
sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
|
983
|
-
brick_namespace_create.call(prefixes, sc.underscore.tr('/', '_').pluralize, options)
|
984
|
-
end
|
985
|
-
|
986
|
-
# Now the API routes if necessary
|
987
|
-
full_resource = nil
|
988
|
-
::Brick.api_roots&.each do |api_root|
|
989
|
-
api_done_views = (versioned_views[api_root] ||= {})
|
990
|
-
found = nil
|
991
|
-
test_ver_num = nil
|
992
|
-
view_relation = nil
|
993
|
-
# If it's a view then see if there's a versioned one available by searching for resource names
|
994
|
-
# versioned with the closest number (equal to or less than) compared with our API version number.
|
995
|
-
if v.key?(:isView)
|
996
|
-
if (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
|
997
|
-
core_object_name = object_name[ver.length + 1..-1]
|
998
|
-
next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
|
999
|
-
|
1000
|
-
# Expect that the last item in the path generally holds versioning information
|
1001
|
-
api_ver = api_root.split('/')[-1]&.gsub('_', '.')
|
1002
|
-
vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
|
1003
|
-
# Was: .to_d
|
1004
|
-
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
|
1005
|
-
# puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
|
1006
|
-
|
1007
|
-
next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
|
1008
|
-
|
1009
|
-
test_ver_num -= 1 until test_ver_num.zero? ||
|
1010
|
-
(view_relation = ::Brick.relations.fetch(
|
1011
|
-
found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
|
1012
|
-
))
|
1013
|
-
api_done_views[unversioned] = nil # Mark that for this API version this view is done
|
1014
|
-
|
1015
|
-
# puts "Found #{found}" if view_relation
|
1016
|
-
# If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
|
1017
|
-
# fall back to simply looking for "v_view_name", and then finally "view_name".
|
1018
|
-
no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
|
1019
|
-
standard_prefix = 'v_'
|
1020
|
-
else
|
1021
|
-
core_object_name = object_name
|
1022
|
-
end
|
1023
|
-
if (rvp = ::Brick.config.api_remove_view_prefix) && core_object_name.start_with?(rvp)
|
1024
|
-
core_object_name.slice!(0, rvp.length)
|
1025
|
-
end
|
1026
|
-
no_prefix_name = "#{schema_prefix}#{core_object_name}"
|
1027
|
-
unversioned = "#{schema_prefix}#{standard_prefix}#{::Brick.config.api_add_view_prefix}#{core_object_name}"
|
1028
|
-
else
|
1029
|
-
unversioned = k
|
1030
|
-
end
|
1031
|
-
|
1032
|
-
view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
|
1033
|
-
(no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil)) ||
|
1034
|
-
(no_prefix_name && ::Brick.relations.fetch(found = no_prefix_name, nil))
|
1035
|
-
if view_relation
|
1036
|
-
actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
|
1037
|
-
# Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
|
1038
|
-
# Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
|
1039
|
-
# these 3 things controls and changes the nature of the endpoint that gets built:
|
1040
|
-
# (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
|
1041
|
-
proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
|
1042
|
-
begin
|
1043
|
-
num_args = filter.arity.negative? ? 6 : filter.arity
|
1044
|
-
filter.call(*[unversioned, k, view_relation, actions, api_ver_num, found, test_ver_num][0...num_args])
|
1045
|
-
rescue StandardError => e
|
1046
|
-
puts "::Brick.api_filter Proc error: #{e.message}"
|
1047
|
-
end
|
1048
|
-
end
|
1049
|
-
# proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
|
1050
|
-
|
1051
|
-
case proc_result
|
1052
|
-
when NilClass
|
1053
|
-
# Do nothing differently than what normal behaviour would be
|
1054
|
-
when FalseClass # Skip implementing this endpoint
|
1055
|
-
view_relation[:api][api_ver_num] = nil
|
1056
|
-
next
|
1057
|
-
when Array # Did they give back an array of actions?
|
1058
|
-
unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
|
1059
|
-
proc_result = [unversioned, to_relation, proc_result]
|
1060
|
-
end
|
1061
|
-
# Otherwise don't change this array because it's probably legit
|
1062
|
-
when String
|
1063
|
-
proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
|
1064
|
-
else
|
1065
|
-
puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n #{proc_result.inspect}"
|
1066
|
-
proc_result = nil # Couldn't understand what in the world was returned
|
1067
|
-
end
|
1068
|
-
|
1069
|
-
if proc_result&.present?
|
1070
|
-
if proc_result[1] # to_other_relation
|
1071
|
-
if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
|
1072
|
-
k = proc_result[1] # Route this call over to this different relation
|
1073
|
-
view_relation = new_view_relation
|
1074
|
-
else
|
1075
|
-
puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
|
1076
|
-
end
|
1077
|
-
end
|
1078
|
-
if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
|
1079
|
-
found = proc_result.first
|
1080
|
-
end
|
1081
|
-
actions &= proc_result[2] if proc_result[2] # allowed_actions
|
1082
|
-
end
|
1083
|
-
(view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
|
1084
|
-
|
1085
|
-
# view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
|
1086
|
-
# first_part[1..-1].gsub('_', '.').to_i
|
1087
|
-
# end
|
1088
|
-
controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
|
1089
|
-
"#{full_schema_prefix}#{last}"
|
1090
|
-
else
|
1091
|
-
found
|
1092
|
-
end.tr('.', '/')
|
1093
|
-
|
1094
|
-
{ :index => 'get', :create => 'post' }.each do |action, method|
|
1095
|
-
if actions.include?(action)
|
1096
|
-
# Normally goes to something like: /api/v1/employees
|
1097
|
-
send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
|
1098
|
-
end
|
1099
|
-
end
|
1100
|
-
# %%% We do not yet surface the #show action
|
1101
|
-
if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
|
1102
|
-
{ :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
|
1103
|
-
if actions.include?(action)
|
1104
|
-
methods.each do |method|
|
1105
|
-
send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
|
1106
|
-
end
|
1107
|
-
end
|
1108
|
-
end
|
1109
|
-
end
|
1110
|
-
end
|
1111
|
-
end
|
1112
|
-
|
1113
|
-
# Trestle compatibility
|
1114
|
-
if Object.const_defined?('Trestle') && ::Trestle.config.options&.key?(:site_title) &&
|
1115
|
-
!Object.const_defined?("#{(res_name = resource_name.tr('/', '_')).camelize}Admin")
|
1116
|
-
begin
|
1117
|
-
::Trestle.resource(res_sym = res_name.to_sym, model: class_name&.constantize) do
|
1118
|
-
menu { item res_sym, icon: "fa fa-star" }
|
1119
|
-
end
|
1120
|
-
rescue
|
1121
|
-
end
|
1122
|
-
end
|
1123
|
-
end
|
1124
|
-
|
1125
|
-
if (named_routes = instance_variable_get(:@set).named_routes).respond_to?(:find)
|
1126
|
-
if ::Brick.config.add_status && (status_as = "#{controller_prefix.tr('/', '_')}brick_status".to_sym)
|
1127
|
-
(
|
1128
|
-
!(status_route = instance_variable_get(:@set).named_routes.find { |route| route.first == status_as }&.last) ||
|
1129
|
-
!status_route.ast.to_s.include?("/#{controller_prefix}brick_status/")
|
1130
|
-
)
|
1131
|
-
get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: status_as.to_s)
|
1132
|
-
end
|
1133
|
-
|
1134
|
-
# ::Brick.config.add_schema &&
|
1135
|
-
if (schema_as = "#{controller_prefix.tr('/', '_')}brick_schema".to_sym)
|
1136
|
-
(
|
1137
|
-
!(schema_route = instance_variable_get(:@set).named_routes.find { |route| route.first == schema_as }&.last) ||
|
1138
|
-
!schema_route.ast.to_s.include?("/#{controller_prefix}brick_schema/")
|
1139
|
-
)
|
1140
|
-
post("/#{controller_prefix}brick_schema", to: 'brick_gem#schema_create', as: schema_as.to_s)
|
1141
|
-
end
|
1142
|
-
|
1143
|
-
if ::Brick.config.add_orphans && (orphans_as = "#{controller_prefix.tr('/', '_')}brick_orphans".to_sym)
|
1144
|
-
(
|
1145
|
-
!(orphans_route = instance_variable_get(:@set).named_routes.find { |route| route.first == orphans_as }&.last) ||
|
1146
|
-
!orphans_route.ast.to_s.include?("/#{controller_prefix}brick_orphans/")
|
1147
|
-
)
|
1148
|
-
get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
|
1149
|
-
end
|
1150
|
-
end
|
1151
|
-
|
1152
|
-
if instance_variable_get(:@set).named_routes.names.exclude?(:brick_crosstab)
|
1153
|
-
get("/#{controller_prefix}brick_crosstab", to: 'brick_gem#crosstab', as: 'brick_crosstab')
|
1154
|
-
get("/#{controller_prefix}brick_crosstab/data", to: 'brick_gem#crosstab_data')
|
1155
|
-
end
|
1156
|
-
|
1157
|
-
if ((rswag_ui_present = Object.const_defined?('Rswag::Ui')) &&
|
1158
|
-
(rswag_path = routeset_to_use.routes.find { |r| r.app.app == ::Rswag::Ui::Engine }
|
1159
|
-
&.instance_variable_get(:@path_formatter)
|
1160
|
-
&.instance_variable_get(:@parts)&.join) &&
|
1161
|
-
(doc_endpoints = ::Rswag::Ui.config.config_object[:urls])) ||
|
1162
|
-
(doc_endpoints = ::Brick.instance_variable_get(:@swagger_endpoints))
|
1163
|
-
last_endpoint_parts = nil
|
1164
|
-
doc_endpoints.each do |doc_endpoint|
|
1165
|
-
puts "Mounting OpenApi 3.0 documentation endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}" unless ::Brick.routes_done
|
1166
|
-
send(:get, doc_endpoint[:url], { to: 'brick_openapi#index' })
|
1167
|
-
endpoint_parts = doc_endpoint[:url]&.split('/')
|
1168
|
-
last_endpoint_parts = endpoint_parts
|
1169
|
-
end
|
1170
|
-
end
|
1171
|
-
return if ::Brick.routes_done
|
1172
|
-
|
1173
|
-
if doc_endpoints.present?
|
1174
|
-
if rswag_ui_present
|
1175
|
-
if rswag_path
|
1176
|
-
puts "API documentation now available when navigating to: /#{last_endpoint_parts&.find(&:present?)}/index.html"
|
1177
|
-
else
|
1178
|
-
puts "In order to make documentation available you can put this into your routes.rb:"
|
1179
|
-
puts " mount Rswag::Ui::Engine => '/#{last_endpoint_parts&.find(&:present?) || 'api-docs'}'"
|
1180
|
-
end
|
1181
|
-
else
|
1182
|
-
puts "Having this exposed, one easy way to leverage this to create HTML-based API documentation is to use Scalar.
|
1183
|
-
It will jump to life when you put these two lines into a view template or other HTML resource:
|
1184
|
-
<script id=\"api-reference\" data-url=\"#{last_endpoint_parts.join('/')}\"></script>
|
1185
|
-
<script src=\"https://cdn.jsdelivr.net/@scalar/api-reference\"></script>
|
1186
|
-
Alternatively you can add the rswag-ui gem."
|
1187
|
-
end
|
1188
|
-
elsif rswag_ui_present
|
1189
|
-
sample_path = rswag_path || '/api-docs'
|
1190
|
-
puts
|
1191
|
-
puts "Brick: rswag-ui gem detected -- to make OpenAPI 3.0 documentation available from a path such as '#{sample_path}/v1/swagger.json',"
|
1192
|
-
puts ' put code such as this in an initializer:'
|
1193
|
-
puts ' Rswag::Ui.configure do |config|'
|
1194
|
-
puts " config.swagger_endpoint '#{sample_path}/v1/swagger.json', 'API V1 Docs'"
|
1195
|
-
puts ' end'
|
1196
|
-
unless rswag_path
|
1197
|
-
puts
|
1198
|
-
puts ' and put this into your routes.rb:'
|
1199
|
-
puts " mount Rswag::Ui::Engine => '/api-docs'"
|
1200
|
-
end
|
1201
|
-
end
|
1202
|
-
|
1203
|
-
puts "\n" if tables.present? || views.present?
|
1204
|
-
if tables.present?
|
1205
|
-
puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
|
1206
|
-
puts "======================================#{' ' * (table_class_length - 38)} ====="
|
1207
|
-
::Brick.display_classes(controller_prefix, tables, table_class_length)
|
1208
|
-
end
|
1209
|
-
if views.present?
|
1210
|
-
puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
|
1211
|
-
puts "=====================================#{' ' * (view_class_length - 37)} ====="
|
1212
|
-
::Brick.display_classes(controller_prefix, views, view_class_length)
|
1213
|
-
end
|
1214
|
-
::Brick.routes_done = true
|
1215
|
-
end
|
1216
|
-
end
|
1217
|
-
|
1218
884
|
end
|
1219
885
|
|
1220
886
|
require 'brick/version_number'
|
@@ -2009,7 +1675,11 @@ end
|
|
2009
1675
|
|
2010
1676
|
# Now the Ransack Polyamorous version of #build
|
2011
1677
|
if Gem::Dependency.new('ransack').matching_specs.present?
|
2012
|
-
|
1678
|
+
begin # First try the new way of requiring Polyamorous
|
1679
|
+
require 'polyamorous/activerecord/join_dependency'
|
1680
|
+
rescue LoadError => e
|
1681
|
+
require "polyamorous/activerecord_#{::ActiveRecord::VERSION::STRING[0, 3]}_ruby_2/join_dependency"
|
1682
|
+
end
|
2013
1683
|
module Polyamorous::JoinDependencyExtensions
|
2014
1684
|
def build(associations, base_klass, root = nil, path = '')
|
2015
1685
|
root ||= associations
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'brick'
|
4
|
+
require 'rails/generators'
|
5
|
+
require 'rails/generators/active_record'
|
6
|
+
require 'fancy_gets'
|
7
|
+
|
8
|
+
module Brick
|
9
|
+
# Auto-generates controllers
|
10
|
+
class ControllersGenerator < ::Rails::Generators::Base
|
11
|
+
include FancyGets
|
12
|
+
# include ::Rails::Generators::Migration
|
13
|
+
|
14
|
+
desc 'Auto-generates controllers'
|
15
|
+
|
16
|
+
def brick_controllers
|
17
|
+
# %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
|
18
|
+
|
19
|
+
::Brick.mode = :on
|
20
|
+
ActiveRecord::Base.establish_connection
|
21
|
+
|
22
|
+
# Load all models and controllers
|
23
|
+
::Brick.eager_load_classes
|
24
|
+
|
25
|
+
# Generate a list of viable controllers that can be chosen
|
26
|
+
longest_length = 0
|
27
|
+
model_info = Hash.new { |h, k| h[k] = {} }
|
28
|
+
tableless = Hash.new { |h, k| h[k] = [] }
|
29
|
+
existing_controllers = ActionController::Base.descendants.reject do |c|
|
30
|
+
c.name.start_with?('Turbo::Native::')
|
31
|
+
end.map(&:name)
|
32
|
+
controllers = ::Brick.relations.each_with_object([]) do |rel, s|
|
33
|
+
next if rel.first.is_a?(Symbol)
|
34
|
+
|
35
|
+
tbl_parts = rel.first.split('.')
|
36
|
+
tbl_parts.shift if [::Brick.default_schema, 'public'].include?(tbl_parts.first)
|
37
|
+
tbl_parts[-1] = tbl_parts[-1].pluralize
|
38
|
+
begin
|
39
|
+
s << ControllerOption.new(tbl_parts.join('/').camelize, rel.last[:class_name].constantize)
|
40
|
+
rescue
|
41
|
+
end
|
42
|
+
end.reject { |c| existing_controllers.include?(c.to_s) }
|
43
|
+
controllers.sort! do |a, b| # Sort first to separate namespaced stuff from the rest, then alphabetically
|
44
|
+
is_a_namespaced = a.to_s.include?('::')
|
45
|
+
is_b_namespaced = b.to_s.include?('::')
|
46
|
+
if is_a_namespaced && !is_b_namespaced
|
47
|
+
1
|
48
|
+
elsif !is_a_namespaced && is_b_namespaced
|
49
|
+
-1
|
50
|
+
else
|
51
|
+
a.to_s <=> b.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
controllers.each do |m| # Find longest name in the list for future use to show lists on the right side of the screen
|
55
|
+
if longest_length < (len = m.to_s.length)
|
56
|
+
longest_length = len
|
57
|
+
end
|
58
|
+
end
|
59
|
+
chosen = gets_list(list: controllers, chosen: controllers.dup)
|
60
|
+
relations = ::Brick.relations
|
61
|
+
chosen.each do |controller_option|
|
62
|
+
if (controller_parts = controller_option.to_s.split('::')).length > 1
|
63
|
+
namespace = controller_parts.first.constantize
|
64
|
+
end
|
65
|
+
_built_controller, code = Object.send(:build_controller, namespace, controller_parts.last, controller_parts.last.pluralize, controller_option.model, relations)
|
66
|
+
path = ['controllers']
|
67
|
+
path.concat(controller_parts.map(&:underscore))
|
68
|
+
dir = +"#{::Rails.root}/app"
|
69
|
+
path[0..-2].each do |path_part|
|
70
|
+
dir << "/#{path_part}"
|
71
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
72
|
+
end
|
73
|
+
File.open("#{dir}/#{path.last}.rb", 'w') { |f| f.write code } unless code.blank?
|
74
|
+
end
|
75
|
+
puts "\n*** Created #{chosen.length} controller files under app/controllers ***"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class ControllerOption
|
81
|
+
attr_accessor :name, :model
|
82
|
+
|
83
|
+
def initialize(name, model)
|
84
|
+
self.name = name
|
85
|
+
self.model = model
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_s
|
89
|
+
name
|
90
|
+
end
|
91
|
+
end
|
@@ -6,12 +6,12 @@ require 'rails/generators/active_record'
|
|
6
6
|
require 'fancy_gets'
|
7
7
|
|
8
8
|
module Brick
|
9
|
-
# Auto-generates models
|
9
|
+
# Auto-generates models
|
10
10
|
class ModelsGenerator < ::Rails::Generators::Base
|
11
11
|
include FancyGets
|
12
12
|
# include ::Rails::Generators::Migration
|
13
13
|
|
14
|
-
desc 'Auto-generates models
|
14
|
+
desc 'Auto-generates models.'
|
15
15
|
|
16
16
|
def brick_models
|
17
17
|
# %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
|
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.192
|
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-12-
|
11
|
+
date: 2023-12-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -248,12 +248,14 @@ files:
|
|
248
248
|
- lib/brick/frameworks/rails/form_tags.rb
|
249
249
|
- lib/brick/frameworks/rspec.rb
|
250
250
|
- lib/brick/join_array.rb
|
251
|
+
- lib/brick/route_mapper.rb
|
251
252
|
- lib/brick/serializers/json.rb
|
252
253
|
- lib/brick/serializers/yaml.rb
|
253
254
|
- lib/brick/tasks/orphans.rake
|
254
255
|
- lib/brick/util.rb
|
255
256
|
- lib/brick/version_number.rb
|
256
257
|
- lib/generators/brick/USAGE
|
258
|
+
- lib/generators/brick/controllers_generator.rb
|
257
259
|
- lib/generators/brick/install_generator.rb
|
258
260
|
- lib/generators/brick/migration_builder.rb
|
259
261
|
- lib/generators/brick/migrations_generator.rb
|