Skip to content

Commit c85545a

Browse files
committed
feat: DB annotate finds any model file in any directory
1 parent b4d2ff9 commit c85545a

File tree

4 files changed

+78
-34
lines changed

4 files changed

+78
-34
lines changed

lib/cli/commands/new_app/files/db_rake.rb

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -166,33 +166,51 @@ def self.content(app_name, db_name)
166166
db = #{app_name}.raw_db_connection
167167
model_file_name = args[:model_file_name]&.to_s
168168
169-
models_dir = #{app_name}.root
169+
app_root_dir = TestApp.root
170+
app_dir = File.join(TestApp.root, "app")
170171
171-
Dir.glob("app/models/**/*.rb").each do |model_file|
172+
Dir.glob("app/**/*.rb").each do |model_file|
172173
next if !model_file_name.nil? && model_file == model_file_name
173174
174-
model_path = File.expand_path(model_file, models_dir)
175-
modules = model_file.gsub("app/models/", "").gsub(".rb", "").split("/").map { |mod| Zeitwerk::Inflector.new.camelize(mod, model_path) }
176-
const_name = modules.join("::")
177-
model_klass = Object.const_get(const_name)
178-
next unless model_klass.ancestors.include?(Kirei::Model)
175+
model_path = File.expand_path(model_file, app_root_dir)
176+
loader = Zeitwerk::Registry.loaders.find { |l| l.tag == "app" }
177+
178+
full_path = File.expand_path(model_file, app_root_dir)
179+
klass_constant_name = loader.inflector.camelize(File.basename(model_file, ".rb"), full_path)
180+
181+
#
182+
# root namespaces in Zeitwerk are flattend, e.g. if "app/models" is a root namespace
183+
# then a file "app/models/airport.rb" is loaded as "::Airport".
184+
# if it weren't a root namespace, it would be "::Models::Airport".
185+
#
186+
root_dir_namespaces = loader.dirs.filter_map { |dir| dir == app_dir ? nil : Pathname.new(dir).relative_path_from(Pathname.new(app_dir)).to_s }
187+
relative_path = Pathname.new(full_path).relative_path_from(Pathname.new(app_dir)).to_s
188+
root_dir_of_model = root_dir_namespaces.find { |root_dir| relative_path.start_with?(root_dir) }
189+
relative_path.sub!("\#{root_dir_of_model}/", "") unless root_dir_of_model.nil? || root_dir_of_model.empty?
190+
191+
namespace_parts = relative_path.split("/")
192+
namespace_parts.pop
193+
namespace_parts.map! { |part| loader.inflector.camelize(part, full_path) }
194+
195+
constant_name = "\#{namespace_parts.join('::')}::\#{klass_constant_name}"
196+
197+
model_klass = Object.const_get(constant_name)
198+
next unless model_klass.respond_to?(:table_name)
179199
180200
table_name = model_klass.table_name
181201
schema = db.schema(table_name)
182202
183203
schema_comments = format_schema_comments(table_name, schema)
204+
file_content = File.read(model_path)
184205
185-
file_contents = File.read(model_path)
186-
187-
# Remove existing schema info comments if present
188-
updated_contents = file_contents.sub(/# == Schema Info\\n(.*?)(\\n#\\n)?\\n(?=\\s*(?:class|module))/m, "")
206+
file_content_without_schema_info = file_content.sub(/# == Schema Info\\n(.*?)(\\n#\\n)?\\n(?=\\s*(?:class|module))/m, "")
189207
190208
# Insert the new schema comments before the module/class definition
191-
first_const = modules.first
192-
first_module_or_class = modules.count == 1 ? "class #{first_const}" : "module #{first_const}"
193-
modified_contents = updated_contents.sub(/(A|\n)(#{first_module_or_class})/m, "\\1#{schema_comments}\n\n\\2")
209+
first_module = namespace_parts.first
210+
first_module_or_class = first_module.nil? ? "class \#{klass_constant_name}" : "module \#{first_module}"
211+
modified_content = file_content_without_schema_info.sub(/(A|\\n)(\#{first_module_or_class})/m, "\\\\1\#{schema_comments}\\n\\n\\\\2")
194212
195-
File.write(model_path, modified_contents)
213+
File.write(model_path, modified_content)
196214
end
197215
end
198216
end
@@ -215,7 +233,7 @@ def format_schema_comments(table_name, schema)
215233
type ||= info[:db_type]
216234
null = info[:allow_null] ? 'null' : 'not null'
217235
primary_key = info[:primary_key] ? ', primary key' : ''
218-
lines << "# \#{name.to_s.ljust(20)}:\#{type} \#{null}\#{primary_key}"
236+
lines << "# \#{name.to_s.ljust(20)}:\#{type.to_s.ljust(20)}\#{null}\#{primary_key}"
219237
end
220238
lines.join("\\n") + "\\n#"
221239
end

lib/cli/commands/start.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ def self.call(args)
1212
app_name = app_name.gsub(/[-\s]/, "_")
1313
app_name = app_name.split("_").map(&:capitalize).join if app_name.include?("_")
1414
NewApp::Execute.call(app_name: app_name)
15+
when "test"
16+
# for internal testing
17+
app_name = args[1] || "TestApp"
18+
# test single services here
1519
else
1620
Kirei::Logging::Logger.logger.info("Unknown command")
1721
end

spec/test_app/app/domain/aviation/models/airport.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
#
66
# Table name: airports
77
#
8-
# id :text not null, primary key
9-
# name :text not null
10-
# latitude :float not null
11-
# longitude :float not null
8+
# id :text not null, primary key
9+
# name :text not null
10+
# latitude :double precision not null
11+
# longitude :double precision not null
1212
#
1313

1414
module Aviation

spec/test_app/lib/tasks/db.rake

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -149,28 +149,51 @@ namespace :db do
149149
db = TestApp.raw_db_connection
150150
model_file_name = args[:model_file_name]&.to_s
151151

152-
models_dir = TestApp.root
152+
app_root_dir = TestApp.root
153+
app_dir = File.join(TestApp.root, "app")
153154

154-
Dir.glob("app/models/*.rb").each do |model_file|
155+
Dir.glob("app/**/*.rb").each do |model_file|
155156
next if !model_file_name.nil? && model_file == model_file_name
156157

157-
model_path = File.expand_path(model_file, models_dir)
158-
model_name = Zeitwerk::Inflector.new.camelize(File.basename(model_file, ".rb"), model_path)
159-
model_klass = Object.const_get(model_name)
158+
model_path = File.expand_path(model_file, app_root_dir)
159+
loader = Zeitwerk::Registry.loaders.find { |l| l.tag == "app" }
160+
161+
full_path = File.expand_path(model_file, app_root_dir)
162+
klass_constant_name = loader.inflector.camelize(File.basename(model_file, ".rb"), full_path)
163+
164+
#
165+
# root namespaces in Zeitwerk are flattend, e.g. if "app/models" is a root namespace
166+
# then a file "app/models/airport.rb" is loaded as "::Airport".
167+
# if it weren't a root namespace, it would be "::Models::Airport".
168+
#
169+
root_dir_namespaces = loader.dirs.filter_map { |dir| dir == app_dir ? nil : Pathname.new(dir).relative_path_from(Pathname.new(app_dir)).to_s }
170+
relative_path = Pathname.new(full_path).relative_path_from(Pathname.new(app_dir)).to_s
171+
root_dir_of_model = root_dir_namespaces.find { |root_dir| relative_path.start_with?(root_dir) }
172+
relative_path.sub!("#{root_dir_of_model}/", "") unless root_dir_of_model.nil? || root_dir_of_model.empty?
173+
174+
namespace_parts = relative_path.split("/")
175+
namespace_parts.pop
176+
namespace_parts.map! { |part| loader.inflector.camelize(part, full_path) }
177+
178+
constant_name = "#{namespace_parts.join('::')}::#{klass_constant_name}"
179+
180+
model_klass = Object.const_get(constant_name)
181+
next unless model_klass.respond_to?(:table_name)
182+
160183
table_name = model_klass.table_name
161184
schema = db.schema(table_name)
162185

163186
schema_comments = format_schema_comments(table_name, schema)
187+
file_content = File.read(model_path)
164188

165-
file_contents = File.read(model_path)
189+
file_content_without_schema_info = file_content.sub(/# == Schema Info\n(.*?)(\n#\n)?\n(?=\s*(?:class|module))/m, "")
166190

167-
# Remove existing schema info comments if present
168-
updated_contents = file_contents.sub(/# == Schema Info\n(.*?)(\n#\n)?\n(?=\s*class)/m, "")
191+
# Insert the new schema comments before the module/class definition
192+
first_module = namespace_parts.first
193+
first_module_or_class = first_module.nil? ? "class #{klass_constant_name}" : "module #{first_module}"
194+
modified_content = file_content_without_schema_info.sub(/(A|\n)(#{first_module_or_class})/m, "\\1#{schema_comments}\n\n\\2")
169195

170-
# Insert the new schema comments before the class definition
171-
modified_contents = updated_contents.sub(/(A|\n)(class #{model_name})/m, "\\1#{schema_comments}\n\n\\2")
172-
173-
File.write(model_path, modified_contents)
196+
File.write(model_path, modified_content)
174197
end
175198
end
176199
end
@@ -193,8 +216,7 @@ def format_schema_comments(table_name, schema)
193216
type ||= info[:db_type]
194217
null = info[:allow_null] ? 'null' : 'not null'
195218
primary_key = info[:primary_key] ? ', primary key' : ''
196-
lines << "# #{name.to_s.ljust(20)}:#{type} #{null}#{primary_key}"
219+
lines << "# #{name.to_s.ljust(20)}:#{type.to_s.ljust(20)}#{null}#{primary_key}"
197220
end
198221
lines.join("\n") + "\n#"
199222
end
200-

0 commit comments

Comments
 (0)