brick 1.0.190 → 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.
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
- :routes_done, :established_drf,
110
+ :established_drf,
111
111
  :is_oracle, :is_eager_loading, :auto_models, :initializer_loaded,
112
112
  :table_name_lookup
113
113
  ::Brick.auto_models = []
@@ -619,6 +619,10 @@ module Brick
619
619
  Brick.config.license = key
620
620
  end
621
621
 
622
+ def omit_empty_tables_in_dropdown=(setting)
623
+ Brick.config.omit_empty_tables_in_dropdown = setting
624
+ end
625
+
622
626
  def always_load_fields=(field_set)
623
627
  Brick.config.always_load_fields = field_set
624
628
  end
@@ -877,339 +881,6 @@ In config/initializers/brick.rb appropriate entries would look something like:
877
881
  end
878
882
  end
879
883
 
880
- module RouteMapper
881
- def add_brick_routes
882
- routeset_to_use = ::Rails.application.routes
883
- path_prefix = ::Brick.config.path_prefix
884
- existing_controllers = routeset_to_use.routes.each_with_object({}) do |r, s|
885
- if r.verb == 'GET' && (c = r.defaults[:controller])
886
- path = r.path.ast.to_s
887
- path = path[0..((path.index('(') || 0) - 1)]
888
- # Skip adding this if it's the default_route_fallback set from the initializers/brick.rb file
889
- next if "#{path}##{r.defaults[:action]}" == ::Brick.established_drf
890
-
891
- # next unless [:index, :show, :new, :edit].incude?(a = r.defaults[:action])
892
-
893
- # c_parts = c.split('/')
894
- # while c_parts.length > 0
895
- # c_dotted = c_parts.join('.')
896
- # if (relation = ::Brick.relations[c_dotted]) # Does it match up with an existing Brick table / resource name?
897
- # puts path
898
- # puts " #{c_dotted}##{r.defaults[:action]}"
899
- # s[c_dotted.tr('.', '/')] = nil
900
- # break
901
- # end
902
- # c_parts.shift
903
- # end
904
- s[c] = nil
905
- end
906
- end
907
-
908
- tables = []
909
- views = []
910
- table_class_length = 38 # Length of "Classes that can be built from tables:"
911
- view_class_length = 37 # Length of "Classes that can be built from views:"
912
-
913
- brick_namespace_create = lambda do |path_names, res_name, options|
914
- if path_names&.present?
915
- if (path_name = path_names.pop).is_a?(Array)
916
- module_name = path_name[1]
917
- path_name = path_name.first
918
- end
919
- send(:scope, { module: module_name || path_name, path: path_name, as: path_name }) do
920
- brick_namespace_create.call(path_names, res_name, options)
921
- end
922
- else
923
- send(:resources, res_name.to_sym, **options)
924
- end
925
- end
926
-
927
- # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
928
- # If auto-controllers and auto-models are both enabled then this makes sense:
929
- controller_prefix = (path_prefix ? "#{path_prefix}/" : '')
930
- sti_subclasses = ::Brick.config.sti_namespace_prefixes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
931
- # Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
932
- s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
933
- end
934
- versioned_views = {} # Track which views have already been done for each api_root
935
- ::Brick.relations.each do |k, v|
936
- next if k.is_a?(Symbol)
937
-
938
- if (schema_name = v.fetch(:schema, nil))
939
- schema_prefix = "#{schema_name}."
940
- end
941
-
942
- resource_name = v.fetch(:resource, nil)
943
- next if !resource_name ||
944
- existing_controllers.key?(
945
- controller_prefix + (resource_name = "#{schema_prefix&.tr('.', '/')}#{resource_name}".pluralize)
946
- )
947
-
948
- object_name = k.split('.').last # Take off any first schema part
949
-
950
- full_schema_prefix = if (full_aps = aps = v.fetch(:auto_prefixed_schema, nil))
951
- aps = aps[0..-2] if aps[-1] == '_'
952
- (schema_prefix&.dup || +'') << "#{aps}."
953
- else
954
- schema_prefix
955
- end
956
-
957
- # Track routes being built
958
- if (class_name = v.fetch(:class_name, nil))
959
- if v.key?(:isView)
960
- view_class_length = class_name.length if class_name.length > view_class_length
961
- views
962
- else
963
- table_class_length = class_name.length if class_name.length > table_class_length
964
- tables
965
- end << [class_name, aps, k.tr('.', '/')[full_aps&.length || 0 .. -1]]
966
- end
967
-
968
- options = {}
969
- options[:only] = [:index, :show] if v.key?(:isView)
970
-
971
- # First do the normal routes
972
- prefixes = []
973
- prefixes << [aps, v[:class_name]&.split('::')[-2]&.underscore] if aps
974
- prefixes << schema_name if schema_name
975
- prefixes << path_prefix if path_prefix
976
- brick_namespace_create.call(prefixes, v[:resource], options)
977
- sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
978
- brick_namespace_create.call(prefixes, sc.underscore.tr('/', '_').pluralize, options)
979
- end
980
-
981
- # Now the API routes if necessary
982
- full_resource = nil
983
- ::Brick.api_roots&.each do |api_root|
984
- api_done_views = (versioned_views[api_root] ||= {})
985
- found = nil
986
- test_ver_num = nil
987
- view_relation = nil
988
- # If it's a view then see if there's a versioned one available by searching for resource names
989
- # versioned with the closest number (equal to or less than) compared with our API version number.
990
- if v.key?(:isView)
991
- if (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
992
- core_object_name = object_name[ver.length + 1..-1]
993
- next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
994
-
995
- # Expect that the last item in the path generally holds versioning information
996
- api_ver = api_root.split('/')[-1]&.gsub('_', '.')
997
- vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
998
- # Was: .to_d
999
- 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
1000
- # puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
1001
-
1002
- next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
1003
-
1004
- test_ver_num -= 1 until test_ver_num.zero? ||
1005
- (view_relation = ::Brick.relations.fetch(
1006
- found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
1007
- ))
1008
- api_done_views[unversioned] = nil # Mark that for this API version this view is done
1009
-
1010
- # puts "Found #{found}" if view_relation
1011
- # If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
1012
- # fall back to simply looking for "v_view_name", and then finally "view_name".
1013
- no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
1014
- standard_prefix = 'v_'
1015
- else
1016
- core_object_name = object_name
1017
- end
1018
- if (rvp = ::Brick.config.api_remove_view_prefix) && core_object_name.start_with?(rvp)
1019
- core_object_name.slice!(0, rvp.length)
1020
- end
1021
- no_prefix_name = "#{schema_prefix}#{core_object_name}"
1022
- unversioned = "#{schema_prefix}#{standard_prefix}#{::Brick.config.api_add_view_prefix}#{core_object_name}"
1023
- else
1024
- unversioned = k
1025
- end
1026
-
1027
- view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
1028
- (no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil)) ||
1029
- (no_prefix_name && ::Brick.relations.fetch(found = no_prefix_name, nil))
1030
- if view_relation
1031
- actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
1032
- # Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
1033
- # Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
1034
- # these 3 things controls and changes the nature of the endpoint that gets built:
1035
- # (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
1036
- proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
1037
- begin
1038
- num_args = filter.arity.negative? ? 6 : filter.arity
1039
- filter.call(*[unversioned, k, view_relation, actions, api_ver_num, found, test_ver_num][0...num_args])
1040
- rescue StandardError => e
1041
- puts "::Brick.api_filter Proc error: #{e.message}"
1042
- end
1043
- end
1044
- # proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
1045
-
1046
- case proc_result
1047
- when NilClass
1048
- # Do nothing differently than what normal behaviour would be
1049
- when FalseClass # Skip implementing this endpoint
1050
- view_relation[:api][api_ver_num] = nil
1051
- next
1052
- when Array # Did they give back an array of actions?
1053
- unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
1054
- proc_result = [unversioned, to_relation, proc_result]
1055
- end
1056
- # Otherwise don't change this array because it's probably legit
1057
- when String
1058
- proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
1059
- else
1060
- puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n #{proc_result.inspect}"
1061
- proc_result = nil # Couldn't understand what in the world was returned
1062
- end
1063
-
1064
- if proc_result&.present?
1065
- if proc_result[1] # to_other_relation
1066
- if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
1067
- k = proc_result[1] # Route this call over to this different relation
1068
- view_relation = new_view_relation
1069
- else
1070
- puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
1071
- end
1072
- end
1073
- if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
1074
- found = proc_result.first
1075
- end
1076
- actions &= proc_result[2] if proc_result[2] # allowed_actions
1077
- end
1078
- (view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
1079
-
1080
- # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
1081
- # first_part[1..-1].gsub('_', '.').to_i
1082
- # end
1083
- controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
1084
- "#{full_schema_prefix}#{last}"
1085
- else
1086
- found
1087
- end.tr('.', '/')
1088
-
1089
- { :index => 'get', :create => 'post' }.each do |action, method|
1090
- if actions.include?(action)
1091
- # Normally goes to something like: /api/v1/employees
1092
- send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
1093
- end
1094
- end
1095
- # %%% We do not yet surface the #show action
1096
- if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
1097
- { :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
1098
- if actions.include?(action)
1099
- methods.each do |method|
1100
- send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
1101
- end
1102
- end
1103
- end
1104
- end
1105
- end
1106
- end
1107
-
1108
- # Trestle compatibility
1109
- if Object.const_defined?('Trestle') && ::Trestle.config.options&.key?(:site_title) &&
1110
- !Object.const_defined?("#{(res_name = resource_name.tr('/', '_')).camelize}Admin")
1111
- begin
1112
- ::Trestle.resource(res_sym = res_name.to_sym, model: class_name&.constantize) do
1113
- menu { item res_sym, icon: "fa fa-star" }
1114
- end
1115
- rescue
1116
- end
1117
- end
1118
- end
1119
-
1120
- if (named_routes = instance_variable_get(:@set).named_routes).respond_to?(:find)
1121
- if ::Brick.config.add_status && (status_as = "#{controller_prefix.tr('/', '_')}brick_status".to_sym)
1122
- (
1123
- !(status_route = instance_variable_get(:@set).named_routes.find { |route| route.first == status_as }&.last) ||
1124
- !status_route.ast.to_s.include?("/#{controller_prefix}brick_status/")
1125
- )
1126
- get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: status_as.to_s)
1127
- end
1128
-
1129
- # ::Brick.config.add_schema &&
1130
- if (schema_as = "#{controller_prefix.tr('/', '_')}brick_schema".to_sym)
1131
- (
1132
- !(schema_route = instance_variable_get(:@set).named_routes.find { |route| route.first == schema_as }&.last) ||
1133
- !schema_route.ast.to_s.include?("/#{controller_prefix}brick_schema/")
1134
- )
1135
- post("/#{controller_prefix}brick_schema", to: 'brick_gem#schema_create', as: schema_as.to_s)
1136
- end
1137
-
1138
- if ::Brick.config.add_orphans && (orphans_as = "#{controller_prefix.tr('/', '_')}brick_orphans".to_sym)
1139
- (
1140
- !(orphans_route = instance_variable_get(:@set).named_routes.find { |route| route.first == orphans_as }&.last) ||
1141
- !orphans_route.ast.to_s.include?("/#{controller_prefix}brick_orphans/")
1142
- )
1143
- get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
1144
- end
1145
- end
1146
-
1147
- if instance_variable_get(:@set).named_routes.names.exclude?(:brick_crosstab)
1148
- get("/#{controller_prefix}brick_crosstab", to: 'brick_gem#crosstab', as: 'brick_crosstab')
1149
- get("/#{controller_prefix}brick_crosstab/data", to: 'brick_gem#crosstab_data')
1150
- end
1151
-
1152
- if ((rswag_ui_present = Object.const_defined?('Rswag::Ui')) &&
1153
- (rswag_path = routeset_to_use.routes.find { |r| r.app.app == ::Rswag::Ui::Engine }
1154
- &.instance_variable_get(:@path_formatter)
1155
- &.instance_variable_get(:@parts)&.join) &&
1156
- (doc_endpoints = ::Rswag::Ui.config.config_object[:urls])) ||
1157
- (doc_endpoints = ::Brick.instance_variable_get(:@swagger_endpoints))
1158
- last_endpoint_parts = nil
1159
- doc_endpoints.each do |doc_endpoint|
1160
- puts "Mounting OpenApi 3.0 documentation endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}" unless ::Brick.routes_done
1161
- send(:get, doc_endpoint[:url], { to: 'brick_openapi#index' })
1162
- endpoint_parts = doc_endpoint[:url]&.split('/')
1163
- last_endpoint_parts = endpoint_parts
1164
- end
1165
- end
1166
- return if ::Brick.routes_done
1167
-
1168
- if doc_endpoints.present?
1169
- if rswag_ui_present
1170
- if rswag_path
1171
- puts "API documentation now available when navigating to: /#{last_endpoint_parts&.find(&:present?)}/index.html"
1172
- else
1173
- puts "In order to make documentation available you can put this into your routes.rb:"
1174
- puts " mount Rswag::Ui::Engine => '/#{last_endpoint_parts&.find(&:present?) || 'api-docs'}'"
1175
- end
1176
- else
1177
- puts "Having this exposed, one easy way to leverage this to create HTML-based API documentation is to use Scalar.
1178
- It will jump to life when you put these two lines into a view template or other HTML resource:
1179
- <script id=\"api-reference\" data-url=\"#{last_endpoint_parts.join('/')}\"></script>
1180
- <script src=\"https://cdn.jsdelivr.net/@scalar/api-reference\"></script>
1181
- Alternatively you can add the rswag-ui gem."
1182
- end
1183
- elsif rswag_ui_present
1184
- sample_path = rswag_path || '/api-docs'
1185
- puts
1186
- puts "Brick: rswag-ui gem detected -- to make OpenAPI 3.0 documentation available from a path such as '#{sample_path}/v1/swagger.json',"
1187
- puts ' put code such as this in an initializer:'
1188
- puts ' Rswag::Ui.configure do |config|'
1189
- puts " config.swagger_endpoint '#{sample_path}/v1/swagger.json', 'API V1 Docs'"
1190
- puts ' end'
1191
- unless rswag_path
1192
- puts
1193
- puts ' and put this into your routes.rb:'
1194
- puts " mount Rswag::Ui::Engine => '/api-docs'"
1195
- end
1196
- end
1197
-
1198
- puts "\n" if tables.present? || views.present?
1199
- if tables.present?
1200
- puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
1201
- puts "======================================#{' ' * (table_class_length - 38)} ====="
1202
- ::Brick.display_classes(controller_prefix, tables, table_class_length)
1203
- end
1204
- if views.present?
1205
- puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
1206
- puts "=====================================#{' ' * (view_class_length - 37)} ====="
1207
- ::Brick.display_classes(controller_prefix, views, view_class_length)
1208
- end
1209
- ::Brick.routes_done = true
1210
- end
1211
- end
1212
-
1213
884
  end
1214
885
 
1215
886
  require 'brick/version_number'
@@ -2004,7 +1675,11 @@ end
2004
1675
 
2005
1676
  # Now the Ransack Polyamorous version of #build
2006
1677
  if Gem::Dependency.new('ransack').matching_specs.present?
2007
- require "polyamorous/activerecord_#{::ActiveRecord::VERSION::STRING[0, 3]}_ruby_2/join_dependency"
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
2008
1683
  module Polyamorous::JoinDependencyExtensions
2009
1684
  def build(associations, base_klass, root = nil, path = '')
2010
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