brick 1.0.191 → 1.0.193
Sign up to get free protection for your applications and to get access to all the features.
- 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 +93 -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: 2a05212d96b2f91b494d68d5273b61397a413daa1804a0bee4a6a13c90443ac5
|
4
|
+
data.tar.gz: 2b5ec052357bddf4a7d8b6b390764a5f29b2078547292cd340733f8a1fe25597
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce5fd867d92ed2df58c8b3c33395ea4517e9441b693af40a4463fb4d0babaf5d5d74ddde213ffe25c9fbc21ca07933f09653620ba33b3c0a80bb8947aab25a22
|
7
|
+
data.tar.gz: bb8e6e40af956eb1fd99f991747069240143cf52002c3af0cc57813c9d4dc0c136773a7901a8dc81dce81505fbf20d6c6ef9b46c01235d281d83158731332feb
|
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,93 @@
|
|
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
|
+
namespace = if (controller_parts = controller_option.to_s.split('::')).length > 1
|
63
|
+
controller_parts.first.constantize
|
64
|
+
else
|
65
|
+
Object
|
66
|
+
end
|
67
|
+
_built_controller, code = Object.send(:build_controller, namespace, controller_parts.last, controller_parts.last.pluralize, controller_option.model, relations)
|
68
|
+
path = ['controllers']
|
69
|
+
path.concat(controller_parts.map(&:underscore))
|
70
|
+
dir = +"#{::Rails.root}/app"
|
71
|
+
path[0..-2].each do |path_part|
|
72
|
+
dir << "/#{path_part}"
|
73
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
74
|
+
end
|
75
|
+
File.open("#{dir}/#{path.last}.rb", 'w') { |f| f.write code } unless code.blank?
|
76
|
+
end
|
77
|
+
puts "\n*** Created #{chosen.length} controller files under app/controllers ***"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ControllerOption
|
83
|
+
attr_accessor :name, :model
|
84
|
+
|
85
|
+
def initialize(name, model)
|
86
|
+
self.name = name
|
87
|
+
self.model = model
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
name
|
92
|
+
end
|
93
|
+
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.193
|
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
|