Skip to content

Commit 7ec51af

Browse files
authored
Refactor generators with helper classes and improvements (#183)
Extract generator logic into focused helper classes following Single Responsibility Principle, and improve code organization and readability. New Helper Classes: - `Fx::Generators::MigrationHelper`, handles migration creation logic - `Fx::Generators::NameHelper`, handles name formatting for migrations - `Fx::Generators::VersionHelper`, manages version tracking and file discovery Generator Improvements: - Simplify `FunctionGenerator` and `TriggerGenerator` by delegating to helpers - Remove inline version regex patterns - Remove inline path definitions - Remove duplicate logic for checking if creating/updating - Remove unused Dir.entries stub in function generator spec - Change SQL query to heredoc format in `TriggerGenerator` Naming Consistency: - s/activerecord_migration_class/active_record_migration_class
1 parent eaa2d29 commit 7ec51af

File tree

14 files changed

+663
-134
lines changed

14 files changed

+663
-134
lines changed

lib/generators/fx/function/function_generator.rb

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
require "rails/generators"
22
require "rails/generators/active_record"
3+
require "generators/fx/version_helper"
4+
require "generators/fx/migration_helper"
5+
require "generators/fx/name_helper"
36

47
module Fx
58
module Generators
@@ -9,35 +12,38 @@ class FunctionGenerator < Rails::Generators::NamedBase
912

1013
source_root File.expand_path("../templates", __FILE__)
1114

15+
DEFINITION_PATH = %w[db functions].freeze
16+
1217
class_option :migration, type: :boolean
1318

1419
def create_functions_directory
15-
unless function_definition_path.exist?
16-
empty_directory(function_definition_path)
17-
end
20+
return if function_definition_path.exist?
21+
22+
empty_directory(function_definition_path)
1823
end
1924

2025
def create_function_definition
21-
if creating_new_function?
22-
create_file definition.path
26+
if version_helper.creating_new?
27+
create_file(definition.path)
2328
else
24-
copy_file previous_definition.full_path, definition.full_path
29+
copy_file(previous_definition.full_path, definition.full_path)
2530
end
2631
end
2732

2833
def create_migration_file
29-
return if skip_migration_creation?
30-
if updating_existing_function?
31-
migration_template(
32-
"db/migrate/update_function.erb",
33-
"db/migrate/update_function_#{file_name}_to_version_#{version}.rb"
34-
)
35-
else
36-
migration_template(
37-
"db/migrate/create_function.erb",
38-
"db/migrate/create_function_#{file_name}.rb"
39-
)
40-
end
34+
return if migration_helper.skip_creation?
35+
36+
template_info = migration_helper.migration_template_info(
37+
object_type: :function,
38+
file_name: file_name,
39+
updating_existing: version_helper.updating_existing?,
40+
version: version_helper.current_version
41+
)
42+
43+
migration_template(
44+
template_info.fetch(:template),
45+
template_info.fetch(:filename)
46+
)
4147
end
4248

4349
def self.next_migration_number(dir)
@@ -46,75 +52,65 @@ def self.next_migration_number(dir)
4652

4753
no_tasks do
4854
def previous_version
49-
@_previous_version ||= Dir.entries(function_definition_path)
50-
.map { |name| version_regex.match(name).try(:[], "version").to_i }
51-
.max
55+
version_helper.previous_version
5256
end
5357

5458
def version
55-
@_version ||= previous_version.next
59+
version_helper.current_version
5660
end
5761

5862
def migration_class_name
59-
if updating_existing_function?
60-
"UpdateFunction#{class_name}ToVersion#{version}"
63+
if version_helper.updating_existing?
64+
migration_helper.update_migration_class_name(
65+
object_type: :function,
66+
class_name: class_name,
67+
version: version
68+
)
6169
else
6270
super
6371
end
6472
end
6573

66-
def activerecord_migration_class
67-
if ActiveRecord::Migration.respond_to?(:current_version)
68-
"ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
69-
else
70-
"ActiveRecord::Migration"
71-
end
74+
def active_record_migration_class
75+
migration_helper.active_record_migration_class
7276
end
7377

7478
def formatted_name
75-
if singular_name.include?(".")
76-
"\"#{singular_name}\""
77-
else
78-
":#{singular_name}"
79-
end
79+
NameHelper.format_for_migration(singular_name)
8080
end
8181
end
8282

8383
private
8484

8585
def function_definition_path
86-
@_function_definition_path ||= Rails.root.join(*%w[db functions])
86+
@_function_definition_path ||= Rails.root.join(*DEFINITION_PATH)
8787
end
8888

89-
def version_regex
90-
/\A#{file_name}_v(?<version>\d+)\.sql\z/
89+
def version_helper
90+
@_version_helper ||= Fx::Generators::VersionHelper.new(
91+
file_name: file_name,
92+
definition_path: function_definition_path
93+
)
9194
end
9295

93-
def updating_existing_function?
94-
previous_version > 0
95-
end
96-
97-
def creating_new_function?
98-
previous_version == 0
96+
def migration_helper
97+
@_migration_helper ||= Fx::Generators::MigrationHelper.new(options)
9998
end
10099

101100
def definition
102-
Fx::Definition.function(name: file_name, version: version)
101+
version_helper.definition_for_version(version: version, type: :function)
103102
end
104103

105104
def previous_definition
106-
Fx::Definition.function(name: file_name, version: previous_version)
105+
version_helper.definition_for_version(version: previous_version, type: :function)
107106
end
108107

109-
# Skip creating migration file if:
110-
# - migrations option is nil or false
111-
def skip_migration_creation?
112-
!migration
108+
def updating_existing_function?
109+
version_helper.updating_existing?
113110
end
114111

115-
# True unless explicitly false
116-
def migration
117-
options[:migration] != false
112+
def creating_new_function?
113+
version_helper.creating_new?
118114
end
119115
end
120116
end

lib/generators/fx/function/templates/db/migrate/create_function.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class <%= migration_class_name %> < <%= activerecord_migration_class %>
1+
class <%= migration_class_name %> < <%= active_record_migration_class %>
22
def change
33
create_function <%= formatted_name %>
44
end

lib/generators/fx/function/templates/db/migrate/update_function.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class <%= migration_class_name %> < <%= activerecord_migration_class %>
1+
class <%= migration_class_name %> < <%= active_record_migration_class %>
22
def change
33
update_function <%= formatted_name %>, version: <%= version %>, revert_to_version: <%= previous_version %>
44
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
module Fx
2+
module Generators
3+
# @api private
4+
class MigrationHelper
5+
def initialize(options)
6+
@options = options
7+
end
8+
9+
def skip_creation?
10+
!should_create_migration?
11+
end
12+
13+
def active_record_migration_class
14+
if ActiveRecord::Migration.respond_to?(:current_version)
15+
"ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
16+
else
17+
"ActiveRecord::Migration"
18+
end
19+
end
20+
21+
def update_migration_class_name(object_type:, class_name:, version:)
22+
"Update#{object_type.capitalize}#{class_name}ToVersion#{version}"
23+
end
24+
25+
def migration_template_info(
26+
object_type:,
27+
file_name:,
28+
updating_existing:,
29+
version:
30+
)
31+
if updating_existing
32+
{
33+
template: "db/migrate/update_#{object_type}.erb",
34+
filename: "db/migrate/update_#{object_type}_#{file_name}_to_version_#{version}.rb"
35+
}
36+
else
37+
{
38+
template: "db/migrate/create_#{object_type}.erb",
39+
filename: "db/migrate/create_#{object_type}_#{file_name}.rb"
40+
}
41+
end
42+
end
43+
44+
private
45+
46+
attr_reader :options
47+
48+
def should_create_migration?
49+
options.fetch(:migration, true)
50+
end
51+
end
52+
end
53+
end

lib/generators/fx/name_helper.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module Fx
2+
module Generators
3+
# @api private
4+
class NameHelper
5+
def self.format_for_migration(name)
6+
if name.include?(".")
7+
"\"#{name}\""
8+
else
9+
":#{name}"
10+
end
11+
end
12+
13+
def self.format_table_name_from_hash(table_hash)
14+
name = table_hash["table_name"] || table_hash["on"]
15+
16+
if name.nil?
17+
raise(
18+
ArgumentError,
19+
"Either `table_name:NAME` or `on:NAME` must be specified"
20+
)
21+
end
22+
23+
format_for_migration(name)
24+
end
25+
26+
def self.validate_and_format(name)
27+
raise ArgumentError, "Name cannot be blank" if name.blank?
28+
29+
format_for_migration(name)
30+
end
31+
end
32+
end
33+
end

lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class <%= migration_class_name %> < <%= activerecord_migration_class %>
1+
class <%= migration_class_name %> < <%= active_record_migration_class %>
22
def change
33
create_trigger <%= formatted_name %>, on: <%= formatted_table_name %>
44
end

lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class <%= migration_class_name %> < <%= activerecord_migration_class %>
1+
class <%= migration_class_name %> < <%= active_record_migration_class %>
22
def change
33
update_trigger <%= formatted_name %>, on: <%= formatted_table_name %>, version: <%= version %>, revert_to_version: <%= previous_version %>
44
end

0 commit comments

Comments
 (0)