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.
- checksums.yaml +4 -4
- data/README.md +7 -11
- data/bin/railsforge +3 -3
- data/lib/railsforge/cli.rb +19 -5
- data/lib/railsforge/cli_minimal.rb +93 -0
- data/lib/railsforge/generators/api_generator.rb +392 -0
- data/lib/railsforge/generators/base_generator.rb +6 -1
- data/lib/railsforge/generators/form_generator.rb +180 -0
- data/lib/railsforge/generators/job_generator.rb +176 -0
- data/lib/railsforge/generators/policy_generator.rb +220 -0
- data/lib/railsforge/generators/presenter_generator.rb +173 -0
- data/lib/railsforge/generators/query_generator.rb +174 -0
- data/lib/railsforge/generators/serializer_generator.rb +166 -0
- data/lib/railsforge/loader.rb +79 -48
- data/lib/railsforge/templates/v3/job/template.rb +1 -1
- data/lib/railsforge/templates/v3/service/template.rb +13 -4
- data/lib/railsforge/version.rb +1 -1
- metadata +20 -41
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ca67789d645855334565249db474d63bfde8607e031642c52a287924c55b9cf
|
|
4
|
+
data.tar.gz: 9517139f8e3224a17ebd875c6606a2ea244e183e289acc1599181eed13522f07
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db1e954e6df040c1856be4cee40357e781201d29a550f31c1dc8924d4c3e93678daecefa42c8e651d02ddef5dc04d4ebd702cb37cac2a0731111ddf0a6e03d13
|
|
7
|
+
data.tar.gz: d7f692d25425099a0ea48499655272a70cf77f517cbfdadbe54826c517aa9e989833774feca6fb7742d36e5aa8bdc47c5c9d9760b8a0130009716ea74298e6c6
|
data/README.md
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
# RailsForge
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="https://github.com/
|
|
5
|
-
<img src="https://img.shields.io/github/actions/workflow/status/
|
|
4
|
+
<a href="https://github.com/mfifth/RailsForge/actions">
|
|
5
|
+
<img src="https://img.shields.io/github/actions/workflow/status/mfifth/railsforge/main.yml?label=CI" alt="CI Status">
|
|
6
6
|
</a>
|
|
7
7
|
<a href="https://rubygems.org/gems/railsforge">
|
|
8
|
-
<img src="https://img.shields.io/gem/v/railsforge" alt="Gem Version">
|
|
8
|
+
<img src="https://img.shields.io/gem/v/railsforge.svg?label=rubygems" alt="Gem Version">
|
|
9
9
|
</a>
|
|
10
|
-
<a href="https://
|
|
11
|
-
<img src="https://img.shields.io/
|
|
12
|
-
</a>
|
|
13
|
-
<a href="https://github.com/railsforge/railsforge/blob/main/LICENSE">
|
|
14
|
-
<img src="https://img.shields.io/github/license/railsforge/railsforge" alt="License">
|
|
10
|
+
<a href="https://github.com/mfifth/RailsForge/blob/main/LICENSE">
|
|
11
|
+
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License">
|
|
15
12
|
</a>
|
|
16
13
|
</p>
|
|
17
14
|
|
|
@@ -41,8 +38,7 @@ RailsForge streamlines Rails development by providing automated generators and a
|
|
|
41
38
|
|
|
42
39
|
### Requirements
|
|
43
40
|
|
|
44
|
-
- **Ruby** version
|
|
45
|
-
- **Rails** version 5.0 or higher (for Rails-specific features)
|
|
41
|
+
- **Ruby** version 3.0 or higher
|
|
46
42
|
- **Bundler** for gem management
|
|
47
43
|
|
|
48
44
|
### Setup Steps
|
|
@@ -464,7 +460,7 @@ We welcome contributions! Please see our contributing guidelines:
|
|
|
464
460
|
|
|
465
461
|
```bash
|
|
466
462
|
# Fork the repository
|
|
467
|
-
git clone https://github.com/
|
|
463
|
+
git clone https://github.com/mfifth/RailsForge.git
|
|
468
464
|
cd railsforge
|
|
469
465
|
|
|
470
466
|
# Install dependencies
|
data/bin/railsforge
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# CLI entrypoint for RailsForge gem
|
|
3
|
-
#
|
|
3
|
+
# Uses minimal loader for fast --help and --version
|
|
4
4
|
|
|
5
5
|
require_relative "../lib/railsforge"
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
RailsForge::
|
|
7
|
+
# Use minimal CLI for fast startup
|
|
8
|
+
RailsForge::CLIMinimal.start(ARGV)
|
data/lib/railsforge/cli.rb
CHANGED
|
@@ -236,15 +236,29 @@ module RailsForge
|
|
|
236
236
|
def self.generate_single(type, name, template_version)
|
|
237
237
|
case type
|
|
238
238
|
when :service
|
|
239
|
-
"Generating service: #{name}
|
|
239
|
+
"Generating service: #{name}\n" + RailsForge::Generators::ServiceGenerator.generate(name, with_spec: true, template_version: template_version)
|
|
240
240
|
when :query
|
|
241
|
-
"Generating query: #{name}"
|
|
241
|
+
"Generating query: #{name}\n" + RailsForge::Generators::QueryGenerator.generate(name, with_spec: true, template_version: template_version)
|
|
242
242
|
when :job
|
|
243
|
-
"Generating job: #{name}"
|
|
243
|
+
"Generating job: #{name}\n" + RailsForge::Generators::JobGenerator.generate(name, with_spec: true, template_version: template_version)
|
|
244
|
+
when :form
|
|
245
|
+
"Generating form: #{name}\n" + RailsForge::Generators::FormGenerator.generate(name, with_spec: true, template_version: template_version)
|
|
246
|
+
when :presenter
|
|
247
|
+
"Generating presenter: #{name}\n" + RailsForge::Generators::PresenterGenerator.generate(name, with_spec: true, template_version: template_version)
|
|
248
|
+
when :policy
|
|
249
|
+
"Generating policy: #{name}\n" + RailsForge::Generators::PolicyGenerator.generate(name, with_spec: true, template_version: template_version)
|
|
250
|
+
when :serializer
|
|
251
|
+
"Generating serializer: #{name}\n" + RailsForge::Generators::SerializerGenerator.generate(name, with_spec: true, template_version: template_version)
|
|
244
252
|
when :component
|
|
245
|
-
"Generating component: #{name}
|
|
253
|
+
"Generating component: #{name}\n" + RailsForge::Generators::ViewComponentGenerator.new(name, template_version: template_version).generate
|
|
246
254
|
when :stimulus
|
|
247
|
-
"Generating stimulus: #{name}
|
|
255
|
+
"Generating stimulus: #{name}\n" + RailsForge::Generators::StimulusControllerGenerator.new(name, template_version: template_version).generate
|
|
256
|
+
when :mailer
|
|
257
|
+
"Generating mailer: #{name}\n" + RailsForge::MailerGenerator.generate(name, with_spec: true)
|
|
258
|
+
when :feature
|
|
259
|
+
"Generating feature: #{name}\n" + RailsForge::FeatureGenerator.generate(name, with_spec: true)
|
|
260
|
+
when :api
|
|
261
|
+
"Generating API: #{name}\n" + RailsForge::Generators::ApiGenerator.generate(name, with_spec: true)
|
|
248
262
|
else
|
|
249
263
|
"Generator for #{type} not fully implemented"
|
|
250
264
|
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Minimal CLI for fast --help and --version
|
|
2
|
+
# This is loaded first, then full CLI is lazy-loaded when needed
|
|
3
|
+
|
|
4
|
+
module RailsForge
|
|
5
|
+
class CLIMinimal
|
|
6
|
+
def self.start(args)
|
|
7
|
+
command = args.shift
|
|
8
|
+
|
|
9
|
+
case command
|
|
10
|
+
when nil, "--help", "-h"
|
|
11
|
+
display_help
|
|
12
|
+
exit 0
|
|
13
|
+
when "--version", "-v"
|
|
14
|
+
# Only load version, nothing else
|
|
15
|
+
require_relative "version"
|
|
16
|
+
puts "RailsForge #{RailsForge::VERSION}"
|
|
17
|
+
exit 0
|
|
18
|
+
else
|
|
19
|
+
# Load full CLI for other commands - this is where the slowness comes from
|
|
20
|
+
require_relative "cli"
|
|
21
|
+
require_relative "profiles"
|
|
22
|
+
require_relative "template_loader"
|
|
23
|
+
require_relative "generator"
|
|
24
|
+
|
|
25
|
+
# Load all generators
|
|
26
|
+
require_relative "generators/base_generator"
|
|
27
|
+
require_relative "generators/service_generator"
|
|
28
|
+
require_relative "generators/view_component_generator"
|
|
29
|
+
require_relative "generators/stimulus_controller_generator"
|
|
30
|
+
require_relative "generators/demo_generator"
|
|
31
|
+
require_relative "generators/devops_generator"
|
|
32
|
+
require_relative "generators/monitoring_generator"
|
|
33
|
+
require_relative "generators/test_generator"
|
|
34
|
+
|
|
35
|
+
# Load all analyzers
|
|
36
|
+
require_relative "analyzers/base_analyzer"
|
|
37
|
+
require_relative "analyzers/controller_analyzer"
|
|
38
|
+
require_relative "analyzers/model_analyzer"
|
|
39
|
+
require_relative "analyzers/spec_analyzer"
|
|
40
|
+
require_relative "analyzers/database_analyzer"
|
|
41
|
+
require_relative "analyzers/metrics_analyzer"
|
|
42
|
+
require_relative "analyzers/refactor_analyzer"
|
|
43
|
+
require_relative "analyzers/security_analyzer"
|
|
44
|
+
require_relative "analyzers/performance_analyzer"
|
|
45
|
+
|
|
46
|
+
# Load refactors and plugins
|
|
47
|
+
require_relative "refactors/refactor_controller"
|
|
48
|
+
require_relative "plugins"
|
|
49
|
+
|
|
50
|
+
# Load legacy modules
|
|
51
|
+
require_relative "mailer_generator"
|
|
52
|
+
require_relative "feature_generator"
|
|
53
|
+
require_relative "api_generator"
|
|
54
|
+
require_relative "audit"
|
|
55
|
+
require_relative "doctor"
|
|
56
|
+
require_relative "graph"
|
|
57
|
+
require_relative "wizard"
|
|
58
|
+
require_relative "wizard_tui"
|
|
59
|
+
require_relative "config"
|
|
60
|
+
|
|
61
|
+
# Dispatch to full CLI
|
|
62
|
+
CLI.start([command] + args)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.display_help
|
|
67
|
+
puts <<~HELP
|
|
68
|
+
RailsForge - A Rails development toolkit
|
|
69
|
+
|
|
70
|
+
Usage:
|
|
71
|
+
railsforge new <app_name> Create a new Rails app
|
|
72
|
+
railsforge generate service <name> Generate a service object
|
|
73
|
+
railsforge generate query <name> Generate a query object
|
|
74
|
+
railsforge generate job <name> Generate a job object
|
|
75
|
+
railsforge generate demo <name> Generate a demo project
|
|
76
|
+
railsforge generate devops Generate Docker/CI-CD configs
|
|
77
|
+
railsforge generate monitoring Generate Sentry/Lograge configs
|
|
78
|
+
railsforge generate test <name> Generate test files
|
|
79
|
+
railsforge analyze Analyze code quality
|
|
80
|
+
railsforge doctor Full project health check
|
|
81
|
+
railsforge audit Architecture audit
|
|
82
|
+
railsforge graph Dependency graph
|
|
83
|
+
railsforge refactor Refactoring suggestions
|
|
84
|
+
railsforge wizard Interactive project setup
|
|
85
|
+
railsforge plugins Plugin management
|
|
86
|
+
railsforge config Configuration
|
|
87
|
+
railsforge profiles List available profiles
|
|
88
|
+
railsforge --version, -v Show version
|
|
89
|
+
railsforge --help, -h Show this help
|
|
90
|
+
HELP
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# API Generator for RailsForge
|
|
2
|
+
# Generates API resources with controllers, serializers, policies, services, and queries
|
|
3
|
+
|
|
4
|
+
require_relative 'base_generator'
|
|
5
|
+
|
|
6
|
+
module RailsForge
|
|
7
|
+
module Generators
|
|
8
|
+
# ApiGenerator creates API resources
|
|
9
|
+
class ApiGenerator < BaseGenerator
|
|
10
|
+
# Error class for invalid resource names
|
|
11
|
+
class InvalidResourceNameError < StandardError; end
|
|
12
|
+
|
|
13
|
+
# Initialize the generator
|
|
14
|
+
# @param name [String] Resource name
|
|
15
|
+
# @param options [Hash] Generator options
|
|
16
|
+
def initialize(name, options = {})
|
|
17
|
+
super(name, options)
|
|
18
|
+
@namespace = options[:namespace] || "api"
|
|
19
|
+
@version = options[:version] || "v1"
|
|
20
|
+
@with_spec = options.fetch(:with_spec, true)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Generate API resource files
|
|
24
|
+
# @return [String] Success message
|
|
25
|
+
def generate
|
|
26
|
+
return "Not in a Rails application directory" unless @base_path
|
|
27
|
+
|
|
28
|
+
validate_name!(@name)
|
|
29
|
+
|
|
30
|
+
results = []
|
|
31
|
+
results << generate_controller
|
|
32
|
+
results << generate_serializer
|
|
33
|
+
results << generate_policy
|
|
34
|
+
results << generate_service
|
|
35
|
+
results << generate_query
|
|
36
|
+
results << generate_request_spec if @with_spec
|
|
37
|
+
|
|
38
|
+
"API resource '#{@name}' generated successfully with #{results.count} files!\n" + results.join("\n")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Class method for CLI
|
|
42
|
+
def self.generate(resource_name, with_spec: true, version: "v1", namespace: "api")
|
|
43
|
+
new(resource_name, with_spec: with_spec, version: version, namespace: namespace).generate
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Validate resource name
|
|
49
|
+
def validate_name!(name)
|
|
50
|
+
raise InvalidResourceNameError, "Resource name cannot be empty" if name.nil? || name.strip.empty?
|
|
51
|
+
raise InvalidResourceNameError, "Name must match pattern: /\\A[A-Z][a-zA-Z0-9]*\\z/" unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Pluralize helper
|
|
55
|
+
def pluralize(word)
|
|
56
|
+
return word + 's' unless word.end_with?('s')
|
|
57
|
+
return word + 'es' if word.end_with?('sh') || word.end_with?('ch')
|
|
58
|
+
word + 's'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Generate controller
|
|
62
|
+
def generate_controller
|
|
63
|
+
controller_dir = File.join(@base_path, "app", "controllers", @namespace, @version)
|
|
64
|
+
FileUtils.mkdir_p(controller_dir)
|
|
65
|
+
|
|
66
|
+
resource_plural = pluralize(@name)
|
|
67
|
+
resource_underscore = underscore
|
|
68
|
+
resource_underscore_plural = pluralize(resource_underscore)
|
|
69
|
+
|
|
70
|
+
file_name = "#{resource_underscore}_controller.rb"
|
|
71
|
+
file_path = File.join(controller_dir, file_name)
|
|
72
|
+
|
|
73
|
+
return " Skipping controller (already exists)" if File.exist?(file_path)
|
|
74
|
+
|
|
75
|
+
content = <<~RUBY
|
|
76
|
+
# API Controller for #{@name}
|
|
77
|
+
# Version: #{@version}
|
|
78
|
+
# Namespace: #{@namespace}
|
|
79
|
+
#
|
|
80
|
+
# Generates standard CRUD actions for REST API
|
|
81
|
+
class #{resource_plural}Controller < ApplicationController
|
|
82
|
+
before_action :set_#{resource_underscore}, only: [:show, :update, :destroy]
|
|
83
|
+
before_action :authenticate_user!, unless: :devise_controller?
|
|
84
|
+
|
|
85
|
+
# GET /#{resource_underscore_plural}
|
|
86
|
+
def index
|
|
87
|
+
@#{resource_underscore_plural} = #{@name}Query.call
|
|
88
|
+
render json: #{@name}Serializer.new(@#{resource_underscore_plural}).serializable_hash
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# GET /#{resource_underscore_plural}/:id
|
|
92
|
+
def show
|
|
93
|
+
render json: #{@name}Serializer.new(@#{resource_underscore}).serializable_hash
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# POST /#{resource_underscore_plural}
|
|
97
|
+
def create
|
|
98
|
+
@result = Create#{@name}Service.call(#{resource_underscore}_params)
|
|
99
|
+
|
|
100
|
+
if @result.success?
|
|
101
|
+
render json: #{@name}Serializer.new(@result.#{resource_underscore}).serializable_hash, status: :created
|
|
102
|
+
else
|
|
103
|
+
render json: { errors: @result.errors }, status: :unprocessable_entity
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# PATCH/PUT /#{resource_underscore_plural}/:id
|
|
108
|
+
def update
|
|
109
|
+
@result = Update#{@name}Service.call(@#{resource_underscore}, #{resource_underscore}_params)
|
|
110
|
+
|
|
111
|
+
if @result.success?
|
|
112
|
+
render json: #{@name}Serializer.new(@result.#{resource_underscore}).serializable_hash
|
|
113
|
+
else
|
|
114
|
+
render json: { errors: @result.errors }, status: :unprocessable_entity
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# DELETE /#{resource_underscore_plural}/:id
|
|
119
|
+
def destroy
|
|
120
|
+
@#{resource_underscore}.destroy
|
|
121
|
+
head :no_content
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def set_#{resource_underscore}
|
|
127
|
+
@#{resource_underscore} = #{@name}.find(params[:id])
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def #{resource_underscore}_params
|
|
131
|
+
params.require(:#{resource_underscore}).permit(:name)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
RUBY
|
|
135
|
+
|
|
136
|
+
File.write(file_path, content)
|
|
137
|
+
" Created app/controllers/#{@namespace}/#{@version}/#{file_name}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Generate serializer
|
|
141
|
+
def generate_serializer
|
|
142
|
+
serializer_dir = File.join(@base_path, "app", "serializers")
|
|
143
|
+
FileUtils.mkdir_p(serializer_dir)
|
|
144
|
+
|
|
145
|
+
file_name = "#{underscore}_serializer.rb"
|
|
146
|
+
file_path = File.join(serializer_dir, file_name)
|
|
147
|
+
|
|
148
|
+
return " Skipping serializer (already exists)" if File.exist?(file_path)
|
|
149
|
+
|
|
150
|
+
content = <<~RUBY
|
|
151
|
+
# Serializer for #{@name}
|
|
152
|
+
class #{@name}Serializer < ApplicationSerializer
|
|
153
|
+
attributes :id, :name, :created_at, :updated_at
|
|
154
|
+
end
|
|
155
|
+
RUBY
|
|
156
|
+
|
|
157
|
+
File.write(file_path, content)
|
|
158
|
+
" Created app/serializers/#{file_name}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Generate policy
|
|
162
|
+
def generate_policy
|
|
163
|
+
policy_dir = File.join(@base_path, "app", "policies")
|
|
164
|
+
FileUtils.mkdir_p(policy_dir)
|
|
165
|
+
|
|
166
|
+
file_name = "#{underscore}_policy.rb"
|
|
167
|
+
file_path = File.join(policy_dir, file_name)
|
|
168
|
+
|
|
169
|
+
return " Skipping policy (already exists)" if File.exist?(file_path)
|
|
170
|
+
|
|
171
|
+
content = <<~RUBY
|
|
172
|
+
# Policy for #{@name}
|
|
173
|
+
class #{@name}Policy
|
|
174
|
+
attr_reader :user, :record
|
|
175
|
+
|
|
176
|
+
def initialize(user, record)
|
|
177
|
+
@user = user
|
|
178
|
+
@record = record
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def index?
|
|
182
|
+
true
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def show?
|
|
186
|
+
true
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def create?
|
|
190
|
+
user.present?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def update?
|
|
194
|
+
user.present?
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def destroy?
|
|
198
|
+
user.present?
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class Scope
|
|
202
|
+
def initialize(user, scope)
|
|
203
|
+
@user = user
|
|
204
|
+
@scope = scope
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def resolve
|
|
208
|
+
scope.all
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
RUBY
|
|
213
|
+
|
|
214
|
+
File.write(file_path, content)
|
|
215
|
+
" Created app/policies/#{file_name}"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Generate service
|
|
219
|
+
def generate_service
|
|
220
|
+
service_dir = File.join(@base_path, "app", "services")
|
|
221
|
+
FileUtils.mkdir_p(service_dir)
|
|
222
|
+
res_underscore = underscore
|
|
223
|
+
|
|
224
|
+
# Create service
|
|
225
|
+
file_name = "create_#{res_underscore}_service.rb"
|
|
226
|
+
file_path = File.join(service_dir, file_name)
|
|
227
|
+
|
|
228
|
+
unless File.exist?(file_path)
|
|
229
|
+
content = <<~RUBY
|
|
230
|
+
# Service for creating #{@name}
|
|
231
|
+
class Create#{@name}Service
|
|
232
|
+
def initialize(params)
|
|
233
|
+
@params = params
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def call
|
|
237
|
+
@#{res_underscore} = #{@name}.create!(@params)
|
|
238
|
+
Result.new(success: true, #{res_underscore}: @#{res_underscore})
|
|
239
|
+
rescue => e
|
|
240
|
+
Result.new(success: false, errors: e.message)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
class Result
|
|
244
|
+
attr_reader :#{res_underscore}, :errors
|
|
245
|
+
def initialize(success:, #{res_underscore}: nil, errors: nil)
|
|
246
|
+
@success = success
|
|
247
|
+
@#{res_underscore} = #{res_underscore}
|
|
248
|
+
@errors = errors
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def success?
|
|
252
|
+
@success
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
RUBY
|
|
257
|
+
|
|
258
|
+
File.write(file_path, content)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Update service
|
|
262
|
+
file_name = "update_#{res_underscore}_service.rb"
|
|
263
|
+
file_path = File.join(service_dir, file_name)
|
|
264
|
+
|
|
265
|
+
unless File.exist?(file_path)
|
|
266
|
+
content = <<~RUBY
|
|
267
|
+
# Service for updating #{@name}
|
|
268
|
+
class Update#{@name}Service
|
|
269
|
+
def initialize(#{res_underscore}, params)
|
|
270
|
+
@#{res_underscore} = #{res_underscore}
|
|
271
|
+
@params = params
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def call
|
|
275
|
+
if @#{res_underscore}.update(@params)
|
|
276
|
+
Result.new(success: true, #{res_underscore}: @#{res_underscore})
|
|
277
|
+
else
|
|
278
|
+
Result.new(success: false, errors: @#{res_underscore}.errors.full_messages)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
class Result
|
|
283
|
+
attr_reader :#{res_underscore}, :errors
|
|
284
|
+
def initialize(success:, #{res_underscore}: nil, errors: nil)
|
|
285
|
+
@success = success
|
|
286
|
+
@#{res_underscore} = #{res_underscore}
|
|
287
|
+
@errors = errors
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def success?
|
|
291
|
+
@success
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
RUBY
|
|
296
|
+
|
|
297
|
+
File.write(file_path, content)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
" Created app/services/create_#{res_underscore}_service.rb and update_#{res_underscore}_service.rb"
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Generate query
|
|
304
|
+
def generate_query
|
|
305
|
+
query_dir = File.join(@base_path, "app", "queries")
|
|
306
|
+
FileUtils.mkdir_p(query_dir)
|
|
307
|
+
|
|
308
|
+
file_name = "find_#{underscore}.rb"
|
|
309
|
+
file_path = File.join(query_dir, file_name)
|
|
310
|
+
|
|
311
|
+
return " Skipping query (already exists)" if File.exist?(file_path)
|
|
312
|
+
|
|
313
|
+
content = <<~RUBY
|
|
314
|
+
# Query for finding #{@name}
|
|
315
|
+
class Find#{@name}
|
|
316
|
+
def initialize(scope: nil)
|
|
317
|
+
@scope = scope || #{@name}.all
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def call
|
|
321
|
+
@scope
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
RUBY
|
|
325
|
+
|
|
326
|
+
File.write(file_path, content)
|
|
327
|
+
" Created app/queries/#{file_name}"
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Generate request spec
|
|
331
|
+
def generate_request_spec
|
|
332
|
+
spec_dir = File.join(@base_path, "spec", "requests", @namespace, @version)
|
|
333
|
+
FileUtils.mkdir_p(spec_dir)
|
|
334
|
+
|
|
335
|
+
resource_plural = pluralize(@name)
|
|
336
|
+
resource_underscore = underscore
|
|
337
|
+
|
|
338
|
+
file_name = "#{resource_underscore}_spec.rb"
|
|
339
|
+
file_path = File.join(spec_dir, file_name)
|
|
340
|
+
|
|
341
|
+
return " Skipping spec (already exists)" if File.exist?(file_path)
|
|
342
|
+
|
|
343
|
+
content = <<~RUBY
|
|
344
|
+
require 'rails_helper'
|
|
345
|
+
|
|
346
|
+
RSpec.describe "#{@namespace.capitalize}::#{@version.capitalize}::#{resource_plural}Controller" do
|
|
347
|
+
let(:user) { User.create!(name: "Test", email: "test@example.com") }
|
|
348
|
+
let(:#{resource_underscore}) { #{@name}.create!(name: "Test #{@name}") }
|
|
349
|
+
|
|
350
|
+
before do
|
|
351
|
+
sign_in user
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
describe "GET /index" do
|
|
355
|
+
it "returns a success response" do
|
|
356
|
+
get :index
|
|
357
|
+
expect(response).to be_successful
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
describe "GET /show" do
|
|
362
|
+
it "returns a success response" do
|
|
363
|
+
get :show, params: { id: #{resource_underscore}.id }
|
|
364
|
+
expect(response).to be_successful
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
describe "POST /create" do
|
|
369
|
+
it "creates a new #{resource_underscore}" do
|
|
370
|
+
expect {
|
|
371
|
+
post :create, params: { #{resource_underscore}: { name: "New #{@name}" } }
|
|
372
|
+
}.to change(#{@name}, :count).by(1)
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
describe "DELETE /destroy" do
|
|
377
|
+
it "deletes the #{resource_underscore}" do
|
|
378
|
+
#{resource_underscore}
|
|
379
|
+
expect {
|
|
380
|
+
delete :destroy, params: { id: #{resource_underscore}.id }
|
|
381
|
+
}.to change(#{@name}, :count).by(-1)
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
RUBY
|
|
386
|
+
|
|
387
|
+
File.write(file_path, content)
|
|
388
|
+
" Created spec/requests/#{@namespace}/#{@version}/#{file_name}"
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
@@ -58,7 +58,12 @@ module RailsForge
|
|
|
58
58
|
# Convert to camelize
|
|
59
59
|
# @return [String] Camelized name
|
|
60
60
|
def camelize
|
|
61
|
-
@name.to_s.
|
|
61
|
+
result = @name.to_s.gsub(/::/, '/')
|
|
62
|
+
result.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
63
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
64
|
+
.split('_')
|
|
65
|
+
.map(&:capitalize)
|
|
66
|
+
.join
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
# Generate the file
|