brick 1.0.56 → 1.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +5 -0
- data/lib/brick/extensions.rb +97 -40
- data/lib/brick/frameworks/rails/engine.rb +50 -1
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +3 -0
- data/lib/generators/brick/migrations_generator.rb +83 -42
- data/lib/generators/brick/{model_generator.rb → models_generator.rb} +40 -32
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db41c2288f8bba8f3cb9c5d2d87bacd4806d1f2401ca9709f85de1886b0db377
|
4
|
+
data.tar.gz: 42b0a7b285a3328ce922daa442e15bf994a382ad0bfc5ed8fe4370ae6b779fbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8baacad02f8ba5c0d9bfe11182d8297c817ba46bef250e172f55a0ff949591abcadda790aecc2614515468a4da468415a8a0e62fd2a60232177dcd3cd63ca03
|
7
|
+
data.tar.gz: e84d2573ec0f95b01dfebf681cec409ec66a1e6052abbb59ebd3130728cad71015c51087f484c55e8e30f3b9a3736d3d9262b7d8cdebf23e5ab702035c0dcbfa
|
data/lib/brick/config.rb
CHANGED
@@ -230,6 +230,11 @@ module Brick
|
|
230
230
|
@mutex.synchronize { @not_nullables = columns }
|
231
231
|
end
|
232
232
|
|
233
|
+
# Add status page showing all resources and what files have been built out for them
|
234
|
+
def add_status
|
235
|
+
true
|
236
|
+
end
|
237
|
+
|
233
238
|
# Add a special page to show references to non-existent records ("orphans")
|
234
239
|
def add_orphans
|
235
240
|
true
|
data/lib/brick/extensions.rb
CHANGED
@@ -646,7 +646,7 @@ Module.class_exec do
|
|
646
646
|
full_class_name << "::#{self.name}" unless self == Object
|
647
647
|
full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
|
648
648
|
if (plural_class_name == 'BrickSwagger' ||
|
649
|
-
(::Brick.config.add_orphans && plural_class_name == 'BrickGem') ||
|
649
|
+
((::Brick.config.add_status || ::Brick.config.add_orphans) && plural_class_name == 'BrickGem') ||
|
650
650
|
model = self.const_get(full_class_name))
|
651
651
|
# if it's a controller and no match or a model doesn't really use the same table name, eager load all models and try to find a model class of the right name.
|
652
652
|
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
@@ -667,45 +667,11 @@ Module.class_exec do
|
|
667
667
|
elsif ::Brick.enable_models?
|
668
668
|
# Custom inheritable Brick base model?
|
669
669
|
class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick')
|
670
|
-
|
671
|
-
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
672
|
-
|
673
|
-
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
674
|
-
base_module != Object # ... or otherwise already in some namespace?
|
675
|
-
schema_name = [(singular_schema_name = name.underscore),
|
676
|
-
(schema_name = singular_schema_name.pluralize),
|
677
|
-
name,
|
678
|
-
name.pluralize].find { |s| Brick.db_schemas.include?(s) }
|
679
|
-
end
|
680
|
-
plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
681
|
-
# If it's namespaced then we turn the first part into what would be a schema name
|
682
|
-
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
|
683
|
-
|
684
|
-
if base_model
|
685
|
-
schema_name = name.underscore # For the auto-STI namespace models
|
686
|
-
table_name = base_model.table_name
|
687
|
-
Object.send(:build_model, base_module, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
|
688
|
-
else
|
689
|
-
# Adjust for STI if we know of a base model for the requested model name
|
690
|
-
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
691
|
-
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
692
|
-
base_model.table_name
|
693
|
-
else
|
694
|
-
ActiveSupport::Inflector.pluralize(singular_table_name)
|
695
|
-
end
|
696
|
-
if ::Brick.apartment_multitenant &&
|
697
|
-
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
698
|
-
schema_name = Apartment.default_schema
|
699
|
-
end
|
700
|
-
# Maybe, just maybe there's a database table that will satisfy this need
|
701
|
-
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
702
|
-
Object.send(:build_model, schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
703
|
-
end
|
704
|
-
end
|
670
|
+
Object.send(:build_model, relations, base_module, name, class_name, inheritable_name)
|
705
671
|
end
|
706
672
|
if result
|
707
673
|
built_class, code = result
|
708
|
-
puts "\n#{code}"
|
674
|
+
puts "\n#{code}\n"
|
709
675
|
built_class
|
710
676
|
elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}") && !schema_name
|
711
677
|
# module_prefixes = type_name.split('::')
|
@@ -727,7 +693,42 @@ class Object
|
|
727
693
|
|
728
694
|
private
|
729
695
|
|
730
|
-
def build_model(
|
696
|
+
def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
|
697
|
+
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
698
|
+
base_module != Object # ... or otherwise already in some namespace?
|
699
|
+
schema_name = [(singular_schema_name = base_name.underscore),
|
700
|
+
(schema_name = singular_schema_name.pluralize),
|
701
|
+
base_name,
|
702
|
+
base_name.pluralize].find { |s| Brick.db_schemas.include?(s) }
|
703
|
+
end
|
704
|
+
plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
705
|
+
# If it's namespaced then we turn the first part into what would be a schema name
|
706
|
+
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
|
707
|
+
|
708
|
+
if base_model
|
709
|
+
schema_name = base_name.underscore # For the auto-STI namespace models
|
710
|
+
table_name = base_model.table_name
|
711
|
+
build_model_worker(base_module, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
|
712
|
+
else
|
713
|
+
# Adjust for STI if we know of a base model for the requested model name
|
714
|
+
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
715
|
+
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
716
|
+
base_model.table_name
|
717
|
+
else
|
718
|
+
ActiveSupport::Inflector.pluralize(singular_table_name)
|
719
|
+
end
|
720
|
+
if ::Brick.apartment_multitenant &&
|
721
|
+
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
722
|
+
schema_name = Apartment.default_schema
|
723
|
+
end
|
724
|
+
# Maybe, just maybe there's a database table that will satisfy this need
|
725
|
+
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
726
|
+
build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
727
|
+
end
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
def build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
731
732
|
if ::Brick.apartment_multitenant &&
|
732
733
|
schema_name == Apartment.default_schema
|
733
734
|
relation = relations["#{schema_name}.#{matching}"]
|
@@ -893,7 +894,7 @@ class Object
|
|
893
894
|
end
|
894
895
|
end
|
895
896
|
end
|
896
|
-
code << "end # model #{full_name}\n
|
897
|
+
code << "end # model #{full_name}\n"
|
897
898
|
[built_model, code]
|
898
899
|
end
|
899
900
|
|
@@ -1002,6 +1003,9 @@ class Object
|
|
1002
1003
|
# Brick-specific pages
|
1003
1004
|
case plural_class_name
|
1004
1005
|
when 'BrickGem'
|
1006
|
+
self.define_method :status do
|
1007
|
+
instance_variable_set(:@resources, ::Brick.get_status_of_resources)
|
1008
|
+
end
|
1005
1009
|
self.define_method :orphans do
|
1006
1010
|
instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params)))
|
1007
1011
|
end
|
@@ -1255,7 +1259,7 @@ class Object
|
|
1255
1259
|
# Get column names for params from relations[model.table_name][:cols].keys
|
1256
1260
|
end
|
1257
1261
|
# end
|
1258
|
-
code << "end # #{namespace_name}#{class_name}\n
|
1262
|
+
code << "end # #{namespace_name}#{class_name}\n"
|
1259
1263
|
end # class definition
|
1260
1264
|
[built_controller, code]
|
1261
1265
|
end
|
@@ -1680,6 +1684,59 @@ module Brick
|
|
1680
1684
|
assoc_bt[:inverse] = assoc_hm
|
1681
1685
|
end
|
1682
1686
|
|
1687
|
+
# Identify built out routes, migrations, models,
|
1688
|
+
# (and also soon controllers and views!)
|
1689
|
+
# for each resource
|
1690
|
+
def get_status_of_resources
|
1691
|
+
rails_root = ::Rails.root.to_s
|
1692
|
+
migrations = if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{rails_root}/db/migrate")
|
1693
|
+
Dir["#{mig_path}/**/*.rb"].each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
|
1694
|
+
File.read(v).split("\n").each do |line|
|
1695
|
+
# For all non-commented lines, look for any that have "create_table", "alter_table", or "drop_table"
|
1696
|
+
if !line.lstrip.start_with?('#') &&
|
1697
|
+
(idx = (line.index('create_table ') || line.index('create_table('))&.+(13)) ||
|
1698
|
+
(idx = (line.index('alter_table ') || line.index('alter_table('))&.+(12)) ||
|
1699
|
+
(idx = (line.index('drop_table ') || line.index('drop_table('))&.+(11))
|
1700
|
+
tbl = line[idx..-1].match(/([:'"\w\.]+)/)&.captures&.first
|
1701
|
+
if tbl
|
1702
|
+
s[tbl.tr(':\'"', '').pluralize] << v
|
1703
|
+
break
|
1704
|
+
end
|
1705
|
+
end
|
1706
|
+
end
|
1707
|
+
end
|
1708
|
+
end
|
1709
|
+
if ::ActiveSupport.version < ::Gem::Version.new('6') ||
|
1710
|
+
::Rails.configuration.instance_variable_get(:@autoloader) == :classic
|
1711
|
+
Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
1712
|
+
else
|
1713
|
+
Zeitwerk::Loader.eager_load_all
|
1714
|
+
end
|
1715
|
+
abstract_activerecord_bases = ActiveRecord::Base.descendants.select { |ar| ar.abstract_class? }.map(&:name)
|
1716
|
+
# abstract_activerecord_bases << ActiveRecord::Base
|
1717
|
+
models = if Dir.exist?(model_path = "#{rails_root}/app/models")
|
1718
|
+
Dir["#{model_path}/**/*.rb"].each_with_object({}) do |v, s|
|
1719
|
+
File.read(v).split("\n").each do |line|
|
1720
|
+
# For all non-commented lines, look for any that start with "class " and also "< ApplicationRecord"
|
1721
|
+
if line.lstrip.start_with?('class') && (idx = line.index('class'))
|
1722
|
+
model = line[idx + 5..-1].match(/[\s:]+([\w:]+)/)&.captures&.first
|
1723
|
+
if model && abstract_activerecord_bases.exclude?(model)
|
1724
|
+
klass = begin
|
1725
|
+
model.constantize
|
1726
|
+
rescue
|
1727
|
+
end
|
1728
|
+
s[model.underscore.tr('/', '.').pluralize] = [
|
1729
|
+
v.start_with?(rails_root) ? v[rails_root.length + 1..-1] : v,
|
1730
|
+
klass
|
1731
|
+
]
|
1732
|
+
end
|
1733
|
+
end
|
1734
|
+
end
|
1735
|
+
end
|
1736
|
+
end
|
1737
|
+
::Brick.relations.keys.map { |v| [(r = v.pluralize), (model = models[r])&.last&.table_name || v, migrations&.fetch(r, nil), model&.first] }
|
1738
|
+
end
|
1739
|
+
|
1683
1740
|
# Locate orphaned records
|
1684
1741
|
def find_orphans(multi_schema)
|
1685
1742
|
is_default_schema = multi_schema&.==(Apartment.default_schema)
|
@@ -56,6 +56,7 @@ module Brick
|
|
56
56
|
# Used by Rails 5.0 and above
|
57
57
|
alias :_brick_template_exists? :template_exists?
|
58
58
|
def template_exists?(*args, **options)
|
59
|
+
(::Brick.config.add_status && args.first == 'status') ||
|
59
60
|
(::Brick.config.add_orphans && args.first == 'orphans') ||
|
60
61
|
_brick_template_exists?(*args, **options) ||
|
61
62
|
# Do not auto-create a template when it's searching for an application.html.erb, which comes in like: ["edit", ["games", "application"]]
|
@@ -96,11 +97,12 @@ module Brick
|
|
96
97
|
@_brick_model ||
|
97
98
|
(ActionView.version < ::Gem::Version.new('5.0') && args[1].is_a?(Array) ? set_brick_model(args) : nil)
|
98
99
|
)&.name) ||
|
100
|
+
(is_status = ::Brick.config.add_status && args[0..1] == ['status', ['brick_gem']]) ||
|
99
101
|
(is_orphans = ::Brick.config.add_orphans && args[0..1] == ['orphans', ['brick_gem']])
|
100
102
|
return _brick_find_template(*args, **options)
|
101
103
|
end
|
102
104
|
|
103
|
-
unless is_orphans
|
105
|
+
unless is_status || is_orphans
|
104
106
|
pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(model_name, nil))
|
105
107
|
obj_name = model_name.split('::').last.underscore
|
106
108
|
path_obj_name = model_name.underscore.tr('/', '_')
|
@@ -708,6 +710,53 @@ if (headerTop) {
|
|
708
710
|
|
709
711
|
#{"<hr><%= link_to \"New #{obj_name}\", new_#{path_obj_name}_path %>" unless @_brick_model.is_view?}
|
710
712
|
#{script}"
|
713
|
+
|
714
|
+
when 'status'
|
715
|
+
# Status page - list of all resources and 5 things they do or don't have present, and what is turned on and off
|
716
|
+
# Must load all models, and then find what table names are represented
|
717
|
+
# Easily could be multiple files involved (STI for instance)
|
718
|
+
+"#{css}
|
719
|
+
<p style=\"color: green\"><%= notice %></p>#{"
|
720
|
+
<select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
|
721
|
+
<select id=\"tbl\">#{table_options}</select>
|
722
|
+
<h1>Status</h1>
|
723
|
+
<table id=\"status\"><thead><tr>
|
724
|
+
<th>Resource</th>
|
725
|
+
<th>Table</th>
|
726
|
+
<th>Migration</th>
|
727
|
+
<th>Model</th>
|
728
|
+
<th>Route</th>
|
729
|
+
<th>Controller</th>
|
730
|
+
<th>Views</th>
|
731
|
+
</tr></thead>
|
732
|
+
<tbody>
|
733
|
+
<% # (listing in schema.rb)
|
734
|
+
# Solid colour if file or route entry is present
|
735
|
+
@resources.each do |r|
|
736
|
+
%>
|
737
|
+
<tr>
|
738
|
+
<td><%= link_to(r[0], \"/#\{r[0].tr('.', '/')}\") %></td>
|
739
|
+
<td<%= if r[1]
|
740
|
+
' class=\"orphan\"' unless ::Brick.relations.key?(r[1])
|
741
|
+
else
|
742
|
+
' class=\"dimmed\"'
|
743
|
+
end&.html_safe %>><%= # Table
|
744
|
+
r[1] %></td>
|
745
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[2] %>><%= # Migration
|
746
|
+
r[2]&.join('<br>')&.html_safe %></td>
|
747
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[3] %>><%= # Model
|
748
|
+
r[3] %></td>
|
749
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[4] %>><%= # Route
|
750
|
+
%></td>
|
751
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[5] %>><%= # Controller
|
752
|
+
%></td>
|
753
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[6] %>><%= # Views
|
754
|
+
%></td>
|
755
|
+
<tr>
|
756
|
+
<% end %>
|
757
|
+
</tbody><table>
|
758
|
+
#{script}"
|
759
|
+
|
711
760
|
when 'orphans'
|
712
761
|
if is_orphans
|
713
762
|
+"#{css}
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -484,6 +484,9 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
484
484
|
send(:resources, controller_name.to_sym, **options)
|
485
485
|
end
|
486
486
|
end
|
487
|
+
if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
|
488
|
+
get('/brick_status', to: 'brick_gem#status', as: 'brick_status')
|
489
|
+
end
|
487
490
|
if ::Brick.config.add_orphans && instance_variable_get(:@set).named_routes.names.exclude?(:brick_orphans)
|
488
491
|
get('/brick_orphans', to: 'brick_gem#orphans', as: 'brick_orphans')
|
489
492
|
end
|
@@ -5,12 +5,14 @@ require 'rails/generators/active_record'
|
|
5
5
|
require 'fancy_gets'
|
6
6
|
|
7
7
|
module Brick
|
8
|
-
# Auto-generates
|
8
|
+
# Auto-generates migration files
|
9
9
|
class MigrationsGenerator < ::Rails::Generators::Base
|
10
10
|
include FancyGets
|
11
11
|
# include ::Rails::Generators::Migration
|
12
12
|
|
13
|
-
# SQL types
|
13
|
+
# Many SQL types are the same as their migration data type name:
|
14
|
+
# text, integer, bigint, date, boolean, decimal, float
|
15
|
+
# These however are not:
|
14
16
|
SQL_TYPES = { 'character varying' => 'string',
|
15
17
|
'character' => 'string', # %%% Need to put in "limit: 1"
|
16
18
|
'xml' => 'text',
|
@@ -23,15 +25,7 @@ module Brick
|
|
23
25
|
'smallint' => 'integer' } # %%% Need to put in "limit: 2"
|
24
26
|
# (Still need to find what "inet" and "json" data types map to.)
|
25
27
|
|
26
|
-
|
27
|
-
# class_option(
|
28
|
-
# :with_changes,
|
29
|
-
# type: :boolean,
|
30
|
-
# default: false,
|
31
|
-
# desc: 'Add IMPORT_TEMPLATE to model'
|
32
|
-
# )
|
33
|
-
|
34
|
-
desc 'Auto-generates migrations for an existing database.'
|
28
|
+
desc 'Auto-generates migration files for an existing database.'
|
35
29
|
|
36
30
|
def brick_migrations
|
37
31
|
# If Apartment is active, see if a default schema to analyse is indicated
|
@@ -47,11 +41,14 @@ module Brick
|
|
47
41
|
key_type = (ActiveRecord.version < ::Gem::Version.new('5.1') ? 'integer' : 'bigint')
|
48
42
|
is_4x_rails = ActiveRecord.version < ::Gem::Version.new('5.0')
|
49
43
|
ar_version = "[#{ActiveRecord.version.segments[0..1].join('.')}]" unless is_4x_rails
|
50
|
-
|
51
|
-
|
44
|
+
is_insert_versions = true
|
45
|
+
is_delete_versions = false
|
46
|
+
versions_to_delete_or_append = nil
|
47
|
+
if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{::Rails.root}/db/migrate")
|
52
48
|
if Dir["#{mig_path}/**/*.rb"].present?
|
53
49
|
puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
|
54
50
|
mig_path2 = "#{::Rails.root}/tmp/brick_migrations"
|
51
|
+
is_insert_versions = false unless mig_path == mig_path2
|
55
52
|
if Dir.exist?(mig_path2)
|
56
53
|
if Dir["#{mig_path2}/**/*.rb"].present?
|
57
54
|
puts "As well, temporary folder #{mig_path2} also has ruby files present."
|
@@ -59,10 +56,15 @@ module Brick
|
|
59
56
|
mig_path2 = gets_list(list: ['Cancel operation!', "Append migration files into #{mig_path} anyway", mig_path, mig_path2])
|
60
57
|
return if mig_path2.start_with?('Cancel')
|
61
58
|
|
59
|
+
existing_mig_files = Dir["#{mig_path2}/**/*.rb"]
|
60
|
+
if (is_insert_versions = mig_path == mig_path2)
|
61
|
+
versions_to_delete_or_append = existing_mig_files.map { |ver| ver.split('/').last.split('_').first }
|
62
|
+
end
|
62
63
|
if mig_path2.start_with?('Append migration files into ')
|
63
64
|
mig_path2 = mig_path
|
64
65
|
else
|
65
|
-
|
66
|
+
is_delete_versions = true
|
67
|
+
existing_mig_files.each { |rb| File.delete(rb) }
|
66
68
|
end
|
67
69
|
else
|
68
70
|
puts "Using temporary folder #{mig_path2} for created migration files.\n\n"
|
@@ -82,13 +84,20 @@ module Brick
|
|
82
84
|
|
83
85
|
# Generate a list of tables that can be chosen
|
84
86
|
chosen = gets_list(list: tables, chosen: tables.dup)
|
87
|
+
schemas = chosen.each_with_object({}) do |v, s|
|
88
|
+
if (v_parts = v.split('.')).length > 1
|
89
|
+
s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
|
90
|
+
end
|
91
|
+
end
|
85
92
|
# Start the timestamps back the same number of minutes from now as expected number of migrations to create
|
86
|
-
current_mig_time = Time.now - chosen.length.minutes
|
93
|
+
current_mig_time = Time.now - (schemas.length + chosen.length).minutes
|
87
94
|
done = []
|
88
95
|
fks = {}
|
89
96
|
stuck = {}
|
90
97
|
indexes = {} # Track index names to make sure things are unique
|
91
|
-
|
98
|
+
built_schemas = {} # Track all built schemas so we can place an appropriate drop_schema command only in the first
|
99
|
+
# migration in which that schema is referenced, thereby allowing rollbacks to function properly.
|
100
|
+
versions_to_create = [] # Resulting versions to be used when updating the schema_migrations table
|
92
101
|
# Start by making migrations for fringe tables (those with no foreign keys).
|
93
102
|
# Continue layer by layer, creating migrations for tables that reference ones already done, until
|
94
103
|
# no more migrations can be created. (At that point hopefully all tables are accounted for.)
|
@@ -113,20 +122,28 @@ module Brick
|
|
113
122
|
end
|
114
123
|
end
|
115
124
|
schema = if (tbl_parts = tbl.split('.')).length > 1
|
116
|
-
if tbl_parts.first == 'public'
|
125
|
+
if tbl_parts.first == (::Brick.default_schema || 'public')
|
117
126
|
tbl_parts.shift
|
118
127
|
nil
|
119
128
|
else
|
120
129
|
tbl_parts.first
|
121
130
|
end
|
122
131
|
end
|
132
|
+
unless schema.blank? || built_schemas.key?(schema)
|
133
|
+
mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
|
134
|
+
migration_file_write(mig_path, "create_db_schema_#{schema}", current_mig_time += 1.minute, ar_version, mig)
|
135
|
+
built_schemas[schema] = nil
|
136
|
+
end
|
137
|
+
|
123
138
|
# %%% For the moment we're skipping polymorphics
|
124
139
|
fkey_cols = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
|
125
|
-
|
140
|
+
# If the primary key is also used as a foreign key, will need to do id: false and then build out
|
141
|
+
# a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
|
142
|
+
# if this one has come in as bigint or integer.
|
143
|
+
pk_is_also_fk = fkey_cols.any? { |assoc| pkey_cols&.first == assoc[:fk] } ? pkey_cols&.first : nil
|
126
144
|
# Support missing primary key (by adding: ,id: false)
|
127
|
-
|
128
|
-
|
129
|
-
unless pkey_cols&.present?
|
145
|
+
id_option = if pk_is_also_fk || (pkey_col_first = relation[:cols][pkey_cols&.first]&.first) != key_type
|
146
|
+
if pk_is_also_fk || !pkey_cols&.present?
|
130
147
|
', id: false'
|
131
148
|
else
|
132
149
|
case pkey_col_first
|
@@ -156,22 +173,12 @@ module Brick
|
|
156
173
|
end
|
157
174
|
# Refer to this table name as a symbol or dotted string as appropriate
|
158
175
|
tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
|
159
|
-
mig
|
160
|
-
mig << " create_schema :#{schema} unless schema_exists?(:#{schema})\n" if schema
|
176
|
+
mig = +" def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
|
161
177
|
mig << " create_table #{tbl_code}#{id_option} do |t|\n"
|
162
178
|
possible_ts = [] # Track possible generic timestamps
|
163
179
|
add_fks = [] # Track foreign keys to add after table creation
|
164
180
|
relation[:cols].each do |col, col_type|
|
165
|
-
|
166
|
-
|
167
|
-
# See if there are generic timestamps
|
168
|
-
if (sql_type = SQL_TYPES[col_type.first]) == 'timestamp' &&
|
169
|
-
['created_at','updated_at'].include?(col)
|
170
|
-
possible_ts << [col, !col_type[3]]
|
171
|
-
next
|
172
|
-
end
|
173
|
-
|
174
|
-
sql_type ||= col_type.first
|
181
|
+
sql_type = SQL_TYPES[col_type.first] || col_type.first
|
175
182
|
suffix = col_type[3] ? +', null: false' : +''
|
176
183
|
if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
|
177
184
|
suffix << ", comment: #{comment.inspect}"
|
@@ -201,7 +208,14 @@ module Brick
|
|
201
208
|
mig << " t.references :#{fk[:assoc_name]}#{suffix}, foreign_key: { to_table: #{to_table} }\n"
|
202
209
|
end
|
203
210
|
else
|
204
|
-
|
211
|
+
next if !id_option&.end_with?('id: false') && pkey_cols.include?(col)
|
212
|
+
|
213
|
+
# See if there are generic timestamps
|
214
|
+
if sql_type == 'timestamp' && ['created_at','updated_at'].include?(col)
|
215
|
+
possible_ts << [col, !col_type[3]]
|
216
|
+
else
|
217
|
+
mig << emit_column(sql_type, col, suffix)
|
218
|
+
end
|
205
219
|
end
|
206
220
|
end
|
207
221
|
if possible_ts.length == 2 && # Both created_at and updated_at
|
@@ -212,6 +226,11 @@ module Brick
|
|
212
226
|
possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
|
213
227
|
end
|
214
228
|
mig << " end\n"
|
229
|
+
if pk_is_also_fk
|
230
|
+
mig << " reversible do |dir|\n"
|
231
|
+
mig << " dir.up { execute('ALTER TABLE #{tbl} ADD PRIMARY KEY (#{pk_is_also_fk})') }\n"
|
232
|
+
mig << " end\n"
|
233
|
+
end
|
215
234
|
add_fks.each do |add_fk|
|
216
235
|
is_commented = false
|
217
236
|
# add_fk[2] holds the inverse relation
|
@@ -221,17 +240,16 @@ module Brick
|
|
221
240
|
# No official PK, but if coincidentally there's a column of the same name, take a chance on it
|
222
241
|
pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
|
223
242
|
end
|
224
|
-
#
|
243
|
+
# to_table column
|
225
244
|
mig << " #{'# ' if is_commented}add_foreign_key #{tbl_code}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
|
226
245
|
end
|
227
|
-
mig << " end\
|
228
|
-
current_mig_time += 1.minute
|
229
|
-
versions << (version = current_mig_time.strftime('%Y%m%d%H%M00'))
|
230
|
-
File.open("#{mig_path}/#{version}_create_#{full_table_name}.rb", "w") { |f| f.write mig }
|
246
|
+
mig << " end\n"
|
247
|
+
versions_to_create << migration_file_write(mig_path, "create_#{tbl_parts.join('_')}", current_mig_time += 1.minute, ar_version, mig)
|
231
248
|
end
|
232
249
|
done.concat(fringe)
|
233
250
|
chosen -= done
|
234
251
|
end
|
252
|
+
|
235
253
|
stuck_counts = Hash.new { |h, k| h[k] = 0 }
|
236
254
|
chosen.each do |leftover|
|
237
255
|
puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
|
@@ -250,9 +268,23 @@ module Brick
|
|
250
268
|
}:"
|
251
269
|
pp stuck_sorted[0..4]
|
252
270
|
else # Successful, and now we can update the schema_migrations table accordingly
|
253
|
-
ActiveRecord::
|
254
|
-
|
255
|
-
|
271
|
+
unless ActiveRecord::Migration.table_exists?(ActiveRecord::Base.schema_migrations_table_name)
|
272
|
+
ActiveRecord::SchemaMigration.create_table
|
273
|
+
end
|
274
|
+
# Remove to_delete - to_create
|
275
|
+
if ((versions_to_delete_or_append ||= []) - versions_to_create).present? && is_delete_versions
|
276
|
+
ActiveRecord::Base.execute_sql("DELETE FROM #{
|
277
|
+
ActiveRecord::Base.schema_migrations_table_name} WHERE version IN (#{
|
278
|
+
(versions_to_delete_or_append - versions_to_create).map { |vtd| "'#{vtd}'" }.join(', ')}
|
279
|
+
)")
|
280
|
+
end
|
281
|
+
# Add to_create - to_delete
|
282
|
+
if is_insert_versions && ((versions_to_create ||= []) - versions_to_delete_or_append).present?
|
283
|
+
ActiveRecord::Base.execute_sql("INSERT INTO #{
|
284
|
+
ActiveRecord::Base.schema_migrations_table_name} (version) VALUES #{
|
285
|
+
(versions_to_create - versions_to_delete_or_append).map { |vtc| "('#{vtc}')" }.join(', ')
|
286
|
+
}")
|
287
|
+
end
|
256
288
|
end
|
257
289
|
end
|
258
290
|
|
@@ -261,5 +293,14 @@ module Brick
|
|
261
293
|
def emit_column(type, name, suffix)
|
262
294
|
" t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
|
263
295
|
end
|
296
|
+
|
297
|
+
def migration_file_write(mig_path, name, current_mig_time, ar_version, mig)
|
298
|
+
File.open("#{mig_path}/#{version = current_mig_time.strftime('%Y%m%d%H%M00')}_#{name}.rb", "w") do |f|
|
299
|
+
f.write "class #{name.camelize} < ActiveRecord::Migration#{ar_version}\n"
|
300
|
+
f.write mig
|
301
|
+
f.write "end\n"
|
302
|
+
end
|
303
|
+
version
|
304
|
+
end
|
264
305
|
end
|
265
306
|
end
|
@@ -6,63 +6,71 @@ require 'fancy_gets'
|
|
6
6
|
|
7
7
|
module Brick
|
8
8
|
# Auto-generates models, controllers, or views
|
9
|
-
class
|
9
|
+
class ModelsGenerator < ::Rails::Generators::Base
|
10
10
|
include FancyGets
|
11
11
|
# include ::Rails::Generators::Migration
|
12
12
|
|
13
|
-
# # source_root File.expand_path('templates', __dir__)
|
14
|
-
# class_option(
|
15
|
-
# :with_changes,
|
16
|
-
# type: :boolean,
|
17
|
-
# default: false,
|
18
|
-
# desc: 'Add IMPORT_TEMPLATE to model'
|
19
|
-
# )
|
20
|
-
|
21
13
|
desc 'Auto-generates models, controllers, or views.'
|
22
14
|
|
23
|
-
def
|
24
|
-
# %%% If Apartment is active, ask which schema they want
|
15
|
+
def brick_models
|
16
|
+
# %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
|
25
17
|
|
26
18
|
# Load all models
|
27
|
-
|
19
|
+
if ::ActiveSupport.version < ::Gem::Version.new('6') ||
|
20
|
+
::Rails.configuration.instance_variable_get(:@autoloader) == :classic
|
21
|
+
Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
22
|
+
else
|
23
|
+
Zeitwerk::Loader.eager_load_all
|
24
|
+
end
|
28
25
|
|
29
26
|
# Generate a list of viable models that can be chosen
|
30
27
|
longest_length = 0
|
31
28
|
model_info = Hash.new { |h, k| h[k] = {} }
|
32
29
|
tableless = Hash.new { |h, k| h[k] = [] }
|
33
|
-
|
34
|
-
|
35
|
-
true
|
36
|
-
elsif !m.table_exists?
|
37
|
-
tableless[m.table_name] << m.name
|
38
|
-
' (No Table)'
|
39
|
-
else
|
40
|
-
this_f_keys = (model_info[m][:f_keys] = m.reflect_on_all_associations.select { |a| a.macro == :belongs_to }) || []
|
41
|
-
column_names = (model_info[m][:column_names] = m.columns.map(&:name) - [m.primary_key, 'created_at', 'updated_at', 'deleted_at'] - this_f_keys.map(&:foreign_key))
|
42
|
-
if column_names.empty? && this_f_keys && !this_f_keys.empty?
|
43
|
-
fk_message = ", although #{this_f_keys.length} foreign keys"
|
44
|
-
" (No columns#{fk_message})"
|
45
|
-
end
|
46
|
-
end
|
47
|
-
# puts "#{m.name}#{trouble}" if trouble&.is_a?(String)
|
48
|
-
trouble
|
30
|
+
existing_models = ActiveRecord::Base.descendants.reject do |m|
|
31
|
+
m.abstract_class? || !m.table_exists? || ::Brick.relations.key?(m.table_name)
|
49
32
|
end
|
33
|
+
models = ::Brick.relations.keys.map do |tbl|
|
34
|
+
tbl_parts = tbl.split('.')
|
35
|
+
tbl_parts[-1] = tbl_parts[-1].singularize
|
36
|
+
tbl_parts.join('/').camelize
|
37
|
+
end - existing_models.map(&:name)
|
50
38
|
models.sort! do |a, b| # Sort first to separate namespaced stuff from the rest, then alphabetically
|
51
|
-
is_a_namespaced = a.
|
52
|
-
is_b_namespaced = b.
|
39
|
+
is_a_namespaced = a.include?('::')
|
40
|
+
is_b_namespaced = b.include?('::')
|
53
41
|
if is_a_namespaced && !is_b_namespaced
|
54
42
|
1
|
55
43
|
elsif !is_a_namespaced && is_b_namespaced
|
56
44
|
-1
|
57
45
|
else
|
58
|
-
a
|
46
|
+
a <=> b
|
59
47
|
end
|
60
48
|
end
|
61
49
|
models.each do |m| # Find longest name in the list for future use to show lists on the right side of the screen
|
62
|
-
if longest_length < (len = m.
|
50
|
+
if longest_length < (len = m.length)
|
63
51
|
longest_length = len
|
64
52
|
end
|
65
53
|
end
|
54
|
+
chosen = gets_list(list: models, chosen: models.dup)
|
55
|
+
relations = ::Brick.relations
|
56
|
+
chosen.each do |model_name|
|
57
|
+
# If we're in a schema then make sure the module file exists
|
58
|
+
base_module = if (model_parts = model_name.split('::')).length > 1
|
59
|
+
"::#{model_parts.first}".constantize
|
60
|
+
else
|
61
|
+
Object
|
62
|
+
end
|
63
|
+
_built_model, code = Object.send(:build_model, relations, base_module, base_module.name, model_parts.last)
|
64
|
+
path = ['models']
|
65
|
+
path.concat(model_parts.map(&:underscore))
|
66
|
+
dir = +"#{::Rails.root}/app"
|
67
|
+
path[0..-2].each do |path_part|
|
68
|
+
dir << "/#{path_part}"
|
69
|
+
Dir.mkdir(dir) unless Dir.exists?(dir)
|
70
|
+
end
|
71
|
+
File.open("#{dir}/#{path.last}.rb", 'w') { |f| f.write code }
|
72
|
+
end
|
73
|
+
puts "\n*** Created #{chosen.length} model files under app/models ***"
|
66
74
|
end
|
67
75
|
|
68
76
|
private
|
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.59
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -239,7 +239,7 @@ files:
|
|
239
239
|
- lib/generators/brick/USAGE
|
240
240
|
- lib/generators/brick/install_generator.rb
|
241
241
|
- lib/generators/brick/migrations_generator.rb
|
242
|
-
- lib/generators/brick/
|
242
|
+
- lib/generators/brick/models_generator.rb
|
243
243
|
- lib/generators/brick/templates/add_object_changes_to_versions.rb.erb
|
244
244
|
- lib/generators/brick/templates/create_versions.rb.erb
|
245
245
|
homepage: https://github.com/lorint/brick
|