railsforge 1.0.0 → 1.0.2

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.
@@ -0,0 +1,173 @@
1
+ # Presenter generator for RailsForge
2
+ # Generates presenter object files
3
+
4
+ require_relative 'base_generator'
5
+
6
+ module RailsForge
7
+ module Generators
8
+ # PresenterGenerator creates presenter files
9
+ class PresenterGenerator < BaseGenerator
10
+ # Error class for invalid presenter names
11
+ class InvalidPresenterNameError < StandardError; end
12
+
13
+ # Template version
14
+ TEMPLATE_VERSION = "v1"
15
+
16
+ # Initialize the generator
17
+ # @param name [String] Presenter name
18
+ # @param options [Hash] Generator options
19
+ def initialize(name, options = {})
20
+ super(name, options)
21
+ @template_version = options[:template_version] || TEMPLATE_VERSION
22
+ @with_spec = options.fetch(:with_spec, true)
23
+ end
24
+
25
+ # Generate presenter files
26
+ # @return [String] Success message
27
+ def generate
28
+ return "Not in a Rails application directory" unless @base_path
29
+
30
+ validate_name!(@name)
31
+
32
+ results = []
33
+ results << generate_presenter
34
+
35
+ if @with_spec
36
+ results << generate_spec
37
+ end
38
+
39
+ "Presenter '#{@name}' generated successfully!\n" + results.join("\n")
40
+ end
41
+
42
+ # Class method for CLI
43
+ def self.generate(presenter_name, with_spec: true, template_version: "v1")
44
+ new(presenter_name, with_spec: with_spec, template_version: template_version).generate
45
+ end
46
+
47
+ private
48
+
49
+ # Validate presenter name
50
+ def validate_name!(name)
51
+ raise InvalidPresenterNameError, "Presenter name cannot be empty" if name.nil? || name.strip.empty?
52
+ raise InvalidPresenterNameError, "Name must match pattern: /\\A[A-Z][a-zA-Z0-9]*\\z/" unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
53
+ end
54
+
55
+ # Generate presenter file
56
+ def generate_presenter
57
+ presenter_dir = File.join(@base_path, "app", "presenters")
58
+ FileUtils.mkdir_p(presenter_dir)
59
+
60
+ file_name = "#{underscore}_presenter.rb"
61
+ file_path = File.join(presenter_dir, file_name)
62
+
63
+ return " Skipping presenter (already exists)" if File.exist?(file_path)
64
+
65
+ content = load_template
66
+ content = apply_template(content)
67
+
68
+ File.write(file_path, content)
69
+ " Created app/presenters/#{file_name}"
70
+ end
71
+
72
+ # Generate spec file
73
+ def generate_spec
74
+ spec_dir = File.join(@base_path, "spec", "presenters")
75
+ FileUtils.mkdir_p(spec_dir)
76
+
77
+ file_name = "#{underscore}_presenter_spec.rb"
78
+ file_path = File.join(spec_dir, file_name)
79
+
80
+ return " Skipping spec (already exists)" if File.exist?(file_path)
81
+
82
+ content = load_spec_template
83
+ content = apply_template(content)
84
+
85
+ File.write(file_path, content)
86
+ " Created spec/presenters/#{file_name}"
87
+ end
88
+
89
+ # Load template content
90
+ def load_template
91
+ template_path = File.join(
92
+ File.dirname(__FILE__),
93
+ "..",
94
+ "templates",
95
+ @template_version,
96
+ "presenter",
97
+ "template.rb"
98
+ )
99
+
100
+ if File.exist?(template_path)
101
+ File.read(template_path)
102
+ else
103
+ default_template
104
+ end
105
+ end
106
+
107
+ # Load spec template
108
+ def load_spec_template
109
+ spec_path = File.join(
110
+ File.dirname(__FILE__),
111
+ "..",
112
+ "templates",
113
+ @template_version,
114
+ "presenter",
115
+ "spec_template.rb"
116
+ )
117
+
118
+ if File.exist?(spec_path)
119
+ File.read(spec_path)
120
+ else
121
+ default_spec_template
122
+ end
123
+ end
124
+
125
+ # Default template
126
+ def default_template
127
+ <<~RUBY
128
+ # Presenter class for #{underscore}
129
+ # Encapsulates view logic
130
+ class #{camelize}Presenter
131
+ def initialize(object)
132
+ @object = object
133
+ end
134
+
135
+ def title
136
+ @object.name.titleize
137
+ end
138
+
139
+ def details
140
+ # TODO: Add presenter logic
141
+ end
142
+ end
143
+ RUBY
144
+ end
145
+
146
+ # Default spec template
147
+ def default_spec_template
148
+ <<~RUBY
149
+ require 'rails_helper'
150
+
151
+ RSpec.describe #{camelize}Presenter do
152
+ let(:object) { double(name: 'Test Object') }
153
+ subject { described_class.new(object) }
154
+
155
+ describe '#title' do
156
+ it 'returns titleized name' do
157
+ expect(subject.title).to eq('Test Object')
158
+ end
159
+ end
160
+ end
161
+ RUBY
162
+ end
163
+
164
+ # Apply template variables
165
+ def apply_template(content)
166
+ content
167
+ .gsub("<%= name %>", @name)
168
+ .gsub("<%= name.camelize %>", camelize)
169
+ .gsub("<%= name.underscore %>", underscore)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,174 @@
1
+ # Query generator for RailsForge
2
+ # Generates query object files
3
+
4
+ require_relative 'base_generator'
5
+
6
+ module RailsForge
7
+ module Generators
8
+ # QueryGenerator creates query files
9
+ class QueryGenerator < BaseGenerator
10
+ # Error class for invalid query names
11
+ class InvalidQueryNameError < StandardError; end
12
+
13
+ # Template version
14
+ TEMPLATE_VERSION = "v1"
15
+
16
+ # Initialize the generator
17
+ # @param name [String] Query name
18
+ # @param options [Hash] Generator options
19
+ def initialize(name, options = {})
20
+ super(name, options)
21
+ @template_version = options[:template_version] || TEMPLATE_VERSION
22
+ @with_spec = options.fetch(:with_spec, true)
23
+ end
24
+
25
+ # Generate query files
26
+ # @return [String] Success message
27
+ def generate
28
+ return "Not in a Rails application directory" unless @base_path
29
+
30
+ validate_name!(@name)
31
+
32
+ results = []
33
+ results << generate_query
34
+
35
+ if @with_spec
36
+ results << generate_spec
37
+ end
38
+
39
+ "Query '#{@name}' generated successfully!\n" + results.join("\n")
40
+ end
41
+
42
+ # Class method for CLI
43
+ def self.generate(query_name, with_spec: true, template_version: "v1")
44
+ new(query_name, with_spec: with_spec, template_version: template_version).generate
45
+ end
46
+
47
+ private
48
+
49
+ # Validate query name
50
+ def validate_name!(name)
51
+ raise InvalidQueryNameError, "Query name cannot be empty" if name.nil? || name.strip.empty?
52
+ raise InvalidQueryNameError, "Name must match pattern: /\\A[A-Z][a-zA-Z0-9]*\\z/" unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
53
+ end
54
+
55
+ # Generate query file
56
+ def generate_query
57
+ query_dir = File.join(@base_path, "app", "queries")
58
+ FileUtils.mkdir_p(query_dir)
59
+
60
+ file_name = "#{underscore}.rb"
61
+ file_path = File.join(query_dir, file_name)
62
+
63
+ return " Skipping query (already exists)" if File.exist?(file_path)
64
+
65
+ content = load_template
66
+ content = apply_template(content)
67
+
68
+ File.write(file_path, content)
69
+ " Created app/queries/#{file_name}"
70
+ end
71
+
72
+ # Generate spec file
73
+ def generate_spec
74
+ spec_dir = File.join(@base_path, "spec", "queries")
75
+ FileUtils.mkdir_p(spec_dir)
76
+
77
+ file_name = "#{underscore}_spec.rb"
78
+ file_path = File.join(spec_dir, file_name)
79
+
80
+ return " Skipping spec (already exists)" if File.exist?(file_path)
81
+
82
+ content = load_spec_template
83
+ content = apply_template(content)
84
+
85
+ File.write(file_path, content)
86
+ " Created spec/queries/#{file_name}"
87
+ end
88
+
89
+ # Load template content
90
+ def load_template
91
+ template_path = File.join(
92
+ File.dirname(__FILE__),
93
+ "..",
94
+ "templates",
95
+ @template_version,
96
+ "query",
97
+ "template.rb"
98
+ )
99
+
100
+ if File.exist?(template_path)
101
+ File.read(template_path)
102
+ else
103
+ default_template
104
+ end
105
+ end
106
+
107
+ # Load spec template
108
+ def load_spec_template
109
+ spec_path = File.join(
110
+ File.dirname(__FILE__),
111
+ "..",
112
+ "templates",
113
+ @template_version,
114
+ "query",
115
+ "spec_template.rb"
116
+ )
117
+
118
+ if File.exist?(spec_path)
119
+ File.read(spec_path)
120
+ else
121
+ default_spec_template
122
+ end
123
+ end
124
+
125
+ # Default template
126
+ def default_template
127
+ <<~RUBY
128
+ # Query class for #{underscore}
129
+ # Encapsulates database queries
130
+ class #{camelize}
131
+ def initialize(scope: nil)
132
+ @scope = scope || default_scope
133
+ end
134
+
135
+ def call
136
+ @scope
137
+ end
138
+
139
+ private
140
+
141
+ def default_scope
142
+ # TODO: Replace with actual model scope
143
+ Model.all
144
+ end
145
+ end
146
+ RUBY
147
+ end
148
+
149
+ # Default spec template
150
+ def default_spec_template
151
+ <<~RUBY
152
+ require 'rails_helper'
153
+
154
+ RSpec.describe #{camelize} do
155
+ describe '#call' do
156
+ it 'returns scope' do
157
+ result = described_class.call
158
+ expect(result).to be_a(ActiveRecord::Relation)
159
+ end
160
+ end
161
+ end
162
+ RUBY
163
+ end
164
+
165
+ # Apply template variables
166
+ def apply_template(content)
167
+ content
168
+ .gsub("<%= name %>", @name)
169
+ .gsub("<%= name.camelize %>", camelize)
170
+ .gsub("<%= name.underscore %>", underscore)
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,166 @@
1
+ # Serializer generator for RailsForge
2
+ # Generates serializer object files
3
+
4
+ require_relative 'base_generator'
5
+
6
+ module RailsForge
7
+ module Generators
8
+ # SerializerGenerator creates serializer files
9
+ class SerializerGenerator < BaseGenerator
10
+ # Error class for invalid serializer names
11
+ class InvalidSerializerNameError < StandardError; end
12
+
13
+ # Template version
14
+ TEMPLATE_VERSION = "v1"
15
+
16
+ # Initialize the generator
17
+ # @param name [String] Serializer name
18
+ # @param options [Hash] Generator options
19
+ def initialize(name, options = {})
20
+ super(name, options)
21
+ @template_version = options[:template_version] || TEMPLATE_VERSION
22
+ @with_spec = options.fetch(:with_spec, true)
23
+ end
24
+
25
+ # Generate serializer files
26
+ # @return [String] Success message
27
+ def generate
28
+ return "Not in a Rails application directory" unless @base_path
29
+
30
+ validate_name!(@name)
31
+
32
+ results = []
33
+ results << generate_serializer
34
+
35
+ if @with_spec
36
+ results << generate_spec
37
+ end
38
+
39
+ "Serializer '#{@name}' generated successfully!\n" + results.join("\n")
40
+ end
41
+
42
+ # Class method for CLI
43
+ def self.generate(serializer_name, with_spec: true, template_version: "v1")
44
+ new(serializer_name, with_spec: with_spec, template_version: template_version).generate
45
+ end
46
+
47
+ private
48
+
49
+ # Validate serializer name
50
+ def validate_name!(name)
51
+ raise InvalidSerializerNameError, "Serializer name cannot be empty" if name.nil? || name.strip.empty?
52
+ raise InvalidSerializerNameError, "Name must match pattern: /\\A[A-Z][a-zA-Z0-9]*\\z/" unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
53
+ end
54
+
55
+ # Generate serializer file
56
+ def generate_serializer
57
+ serializer_dir = File.join(@base_path, "app", "serializers")
58
+ FileUtils.mkdir_p(serializer_dir)
59
+
60
+ file_name = "#{underscore}_serializer.rb"
61
+ file_path = File.join(serializer_dir, file_name)
62
+
63
+ return " Skipping serializer (already exists)" if File.exist?(file_path)
64
+
65
+ content = load_template
66
+ content = apply_template(content)
67
+
68
+ File.write(file_path, content)
69
+ " Created app/serializers/#{file_name}"
70
+ end
71
+
72
+ # Generate spec file
73
+ def generate_spec
74
+ spec_dir = File.join(@base_path, "spec", "serializers")
75
+ FileUtils.mkdir_p(spec_dir)
76
+
77
+ file_name = "#{underscore}_serializer_spec.rb"
78
+ file_path = File.join(spec_dir, file_name)
79
+
80
+ return " Skipping spec (already exists)" if File.exist?(file_path)
81
+
82
+ content = load_spec_template
83
+ content = apply_template(content)
84
+
85
+ File.write(file_path, content)
86
+ " Created spec/serializers/#{file_name}"
87
+ end
88
+
89
+ # Load template content
90
+ def load_template
91
+ template_path = File.join(
92
+ File.dirname(__FILE__),
93
+ "..",
94
+ "templates",
95
+ @template_version,
96
+ "serializer",
97
+ "template.rb"
98
+ )
99
+
100
+ if File.exist?(template_path)
101
+ File.read(template_path)
102
+ else
103
+ default_template
104
+ end
105
+ end
106
+
107
+ # Load spec template
108
+ def load_spec_template
109
+ spec_path = File.join(
110
+ File.dirname(__FILE__),
111
+ "..",
112
+ "templates",
113
+ @template_version,
114
+ "serializer",
115
+ "spec_template.rb"
116
+ )
117
+
118
+ if File.exist?(spec_path)
119
+ File.read(spec_path)
120
+ else
121
+ default_spec_template
122
+ end
123
+ end
124
+
125
+ # Default template
126
+ def default_template
127
+ <<~RUBY
128
+ # Serializer class for #{underscore}
129
+ # Defines JSON serialization
130
+ class #{camelize}Serializer < ApplicationSerializer
131
+ attributes :id, :name, :created_at, :updated_at
132
+
133
+ # TODO and: Add associations methods
134
+ # belongs_to :user
135
+ # has_many :items
136
+ end
137
+ RUBY
138
+ end
139
+
140
+ # Default spec template
141
+ def default_spec_template
142
+ <<~RUBY
143
+ require 'rails_helper'
144
+
145
+ RSpec.describe #{camelize}Serializer do
146
+ let(:resource) { #{camelize}.create!(name: 'Test') }
147
+ let(:serializer) { described_class.new(resource) }
148
+ let(:serialization) { ActiveModel::SerializableResource.new(resource).as_json }
149
+
150
+ it 'includes expected attributes' do
151
+ expect(serialization[:#{camelize.underscore}] [:name]).to eq('Test')
152
+ end
153
+ end
154
+ RUBY
155
+ end
156
+
157
+ # Apply template variables
158
+ def apply_template(content)
159
+ content
160
+ .gsub("<%= name %>", @name)
161
+ .gsub("<%= name.camelize %>", camelize)
162
+ .gsub("<%= name.underscore %>", underscore)
163
+ end
164
+ end
165
+ end
166
+ end
@@ -1,56 +1,87 @@
1
1
  # RailsForge modular loader
2
- # This file centralizes all require statements
2
+ # This file centralizes all require statements with lazy loading
3
3
 
4
4
  require "fileutils"
5
5
 
6
6
  # Get the lib directory path
7
7
  LIB_DIR = File.dirname(__FILE__)
8
8
 
9
- # Core modules
9
+ # Core modules - always needed
10
10
  require_relative "version"
11
- require_relative "profiles"
12
- require_relative "template_loader"
13
-
14
- # CLI
15
- require_relative "cli"
16
-
17
- # Generators (modular)
18
- require_relative "generators/base_generator"
19
- require_relative "generators/service_generator"
20
- require_relative "generators/view_component_generator"
21
- require_relative "generators/stimulus_controller_generator"
22
- require_relative "generators/demo_generator"
23
- require_relative "generators/devops_generator"
24
- require_relative "generators/monitoring_generator"
25
- require_relative "generators/test_generator"
26
-
27
- # Analyzers (modular)
28
- require_relative "analyzers/base_analyzer"
29
- require_relative "analyzers/controller_analyzer"
30
- require_relative "analyzers/model_analyzer"
31
- require_relative "analyzers/spec_analyzer"
32
- require_relative "analyzers/database_analyzer"
33
- require_relative "analyzers/metrics_analyzer"
34
- require_relative "analyzers/refactor_analyzer"
35
- require_relative "analyzers/security_analyzer"
36
- require_relative "analyzers/performance_analyzer"
37
-
38
- # Refactors
39
- require_relative "refactors/refactor_controller"
40
-
41
- # Plugins
42
- require_relative "plugins"
43
-
44
- # Additional features (legacy compatibility)
45
- require_relative "mailer_generator"
46
- require_relative "feature_generator"
47
- require_relative "api_generator"
48
- require_relative "audit"
49
- require_relative "doctor"
50
- require_relative "graph"
51
- require_relative "wizard"
52
- require_relative "wizard_tui"
53
- require_relative "config"
54
-
55
- # Generator module (app creation)
56
- require_relative "generator"
11
+
12
+ # Minimal CLI loader for --help and --version (fast)
13
+ require_relative "cli_minimal"
14
+
15
+ # Lazy-load everything else
16
+ module RailsForge
17
+ class << self
18
+ # Lazy-load generators
19
+ def require_generator(name)
20
+ require_relative "generators/#{name}"
21
+ end
22
+
23
+ # Lazy-load analyzers
24
+ def require_analyzer(name)
25
+ require_relative "analyzers/#{name}"
26
+ end
27
+
28
+ # Lazy-load other modules
29
+ def require_module(name)
30
+ require_relative name
31
+ end
32
+
33
+ # Preload all (for commands that need everything)
34
+ def preload_all!
35
+ require_relative "profiles"
36
+ require_relative "template_loader"
37
+
38
+ # Define Generators module first to ensure proper namespace
39
+ require_relative "generators/base_generator"
40
+
41
+ # Generators
42
+ require_relative "generators/service_generator"
43
+ require_relative "generators/job_generator"
44
+ require_relative "generators/query_generator"
45
+ require_relative "generators/form_generator"
46
+ require_relative "generators/presenter_generator"
47
+ require_relative "generators/policy_generator"
48
+ require_relative "generators/serializer_generator"
49
+ require_relative "generators/api_generator"
50
+ require_relative "generators/view_component_generator"
51
+ require_relative "generators/stimulus_controller_generator"
52
+ require_relative "generators/demo_generator"
53
+ require_relative "generators/devops_generator"
54
+ require_relative "generators/monitoring_generator"
55
+ require_relative "generators/test_generator"
56
+
57
+ # Analyzers
58
+ require_relative "analyzers/base_analyzer"
59
+ require_relative "analyzers/controller_analyzer"
60
+ require_relative "analyzers/model_analyzer"
61
+ require_relative "analyzers/spec_analyzer"
62
+ require_relative "analyzers/database_analyzer"
63
+ require_relative "analyzers/metrics_analyzer"
64
+ require_relative "analyzers/refactor_analyzer"
65
+ require_relative "analyzers/security_analyzer"
66
+ require_relative "analyzers/performance_analyzer"
67
+
68
+ # Refactors & plugins
69
+ require_relative "refactors/refactor_controller"
70
+ require_relative "plugins"
71
+
72
+ # Legacy (modules with class methods)
73
+ require_relative "mailer_generator"
74
+ require_relative "feature_generator"
75
+ require_relative "audit"
76
+ require_relative "doctor"
77
+ require_relative "graph"
78
+ require_relative "wizard"
79
+ require_relative "wizard_tui"
80
+ require_relative "config"
81
+ require_relative "generator"
82
+
83
+ # CLI
84
+ require_relative "cli"
85
+ end
86
+ end
87
+ end
@@ -23,7 +23,7 @@ class <%= class_name %>Job < ApplicationJob
23
23
  # TODO: Implement job logic
24
24
  # Example:
25
25
  # user = User.find(args[:user_id])
26
- # user.send_welcome_email
26
+ # UserMailer.welcome(user).deliver_now
27
27
  Rails.logger.info("Executing #{self.class.name}")
28
28
  end
29
29
 
@@ -46,16 +46,25 @@ class <%= class_name %>
46
46
  private
47
47
 
48
48
  # Validate inputs - returns Result
49
+ # Override this method to add custom validation
50
+ # @example
51
+ # def validate!
52
+ # return Failure(errors: ['Name required']) if @params[:name].blank?
53
+ # Success(true)
54
+ # end
49
55
  def validate!
50
- # TODO: Add validation logic
51
- # Example:
52
- # return Failure(errors: ['Name required']) if @params[:name].blank?
53
56
  Success(true)
54
57
  end
55
58
 
56
59
  # Process the main logic - returns Result
60
+ # Override this method to implement your service logic
61
+ # @example
62
+ # def process!
63
+ # user = User.create!(@params)
64
+ # @result = { user: user }
65
+ # Success(true)
66
+ # end
57
67
  def process!
58
- # TODO: Implement service logic
59
68
  @result = {}
60
69
  Success(true)
61
70
  end
@@ -1,5 +1,5 @@
1
1
  # Version module for RailsForge gem
2
2
  # Defines the current version of the gem
3
3
  module RailsForge
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end