railsforge 1.0.0

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +528 -0
  4. data/bin/railsforge +8 -0
  5. data/lib/railsforge/analyzers/base_analyzer.rb +41 -0
  6. data/lib/railsforge/analyzers/controller_analyzer.rb +83 -0
  7. data/lib/railsforge/analyzers/database_analyzer.rb +55 -0
  8. data/lib/railsforge/analyzers/metrics_analyzer.rb +55 -0
  9. data/lib/railsforge/analyzers/model_analyzer.rb +74 -0
  10. data/lib/railsforge/analyzers/performance_analyzer.rb +161 -0
  11. data/lib/railsforge/analyzers/refactor_analyzer.rb +118 -0
  12. data/lib/railsforge/analyzers/security_analyzer.rb +169 -0
  13. data/lib/railsforge/analyzers/spec_analyzer.rb +58 -0
  14. data/lib/railsforge/api_generator.rb +397 -0
  15. data/lib/railsforge/audit.rb +289 -0
  16. data/lib/railsforge/cli.rb +671 -0
  17. data/lib/railsforge/config.rb +181 -0
  18. data/lib/railsforge/database_analyzer.rb +300 -0
  19. data/lib/railsforge/doctor.rb +250 -0
  20. data/lib/railsforge/feature_generator.rb +560 -0
  21. data/lib/railsforge/generator.rb +313 -0
  22. data/lib/railsforge/generators/base_generator.rb +70 -0
  23. data/lib/railsforge/generators/demo_generator.rb +307 -0
  24. data/lib/railsforge/generators/devops_generator.rb +287 -0
  25. data/lib/railsforge/generators/monitoring_generator.rb +134 -0
  26. data/lib/railsforge/generators/service_generator.rb +122 -0
  27. data/lib/railsforge/generators/stimulus_controller_generator.rb +129 -0
  28. data/lib/railsforge/generators/test_generator.rb +289 -0
  29. data/lib/railsforge/generators/view_component_generator.rb +169 -0
  30. data/lib/railsforge/graph.rb +270 -0
  31. data/lib/railsforge/loader.rb +56 -0
  32. data/lib/railsforge/mailer_generator.rb +191 -0
  33. data/lib/railsforge/plugins/plugin_loader.rb +60 -0
  34. data/lib/railsforge/plugins.rb +30 -0
  35. data/lib/railsforge/profiles/admin_app.yml +49 -0
  36. data/lib/railsforge/profiles/api_only.yml +47 -0
  37. data/lib/railsforge/profiles/blog.yml +47 -0
  38. data/lib/railsforge/profiles/standard.yml +44 -0
  39. data/lib/railsforge/profiles.rb +99 -0
  40. data/lib/railsforge/refactor_analyzer.rb +401 -0
  41. data/lib/railsforge/refactor_controller.rb +277 -0
  42. data/lib/railsforge/refactors/refactor_controller.rb +117 -0
  43. data/lib/railsforge/template_loader.rb +105 -0
  44. data/lib/railsforge/templates/v1/form/spec_template.rb +18 -0
  45. data/lib/railsforge/templates/v1/form/template.rb +28 -0
  46. data/lib/railsforge/templates/v1/job/spec_template.rb +17 -0
  47. data/lib/railsforge/templates/v1/job/template.rb +13 -0
  48. data/lib/railsforge/templates/v1/policy/spec_template.rb +41 -0
  49. data/lib/railsforge/templates/v1/policy/template.rb +57 -0
  50. data/lib/railsforge/templates/v1/presenter/spec_template.rb +12 -0
  51. data/lib/railsforge/templates/v1/presenter/template.rb +13 -0
  52. data/lib/railsforge/templates/v1/query/spec_template.rb +12 -0
  53. data/lib/railsforge/templates/v1/query/template.rb +16 -0
  54. data/lib/railsforge/templates/v1/serializer/spec_template.rb +13 -0
  55. data/lib/railsforge/templates/v1/serializer/template.rb +11 -0
  56. data/lib/railsforge/templates/v1/service/spec_template.rb +12 -0
  57. data/lib/railsforge/templates/v1/service/template.rb +25 -0
  58. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +35 -0
  59. data/lib/railsforge/templates/v1/view_component/template.rb +24 -0
  60. data/lib/railsforge/templates/v2/job/template.rb +49 -0
  61. data/lib/railsforge/templates/v2/query/template.rb +66 -0
  62. data/lib/railsforge/templates/v2/service/spec_template.rb +33 -0
  63. data/lib/railsforge/templates/v2/service/template.rb +71 -0
  64. data/lib/railsforge/templates/v3/job/template.rb +72 -0
  65. data/lib/railsforge/templates/v3/query/spec_template.rb +54 -0
  66. data/lib/railsforge/templates/v3/query/template.rb +115 -0
  67. data/lib/railsforge/templates/v3/service/spec_template.rb +51 -0
  68. data/lib/railsforge/templates/v3/service/template.rb +84 -0
  69. data/lib/railsforge/version.rb +5 -0
  70. data/lib/railsforge/wizard.rb +265 -0
  71. data/lib/railsforge/wizard_tui.rb +286 -0
  72. data/lib/railsforge.rb +13 -0
  73. metadata +216 -0
@@ -0,0 +1,277 @@
1
+ # RefactorController module for RailsForge
2
+ # Automatically refactors fat controllers by extracting service objects
3
+
4
+ require 'fileutils'
5
+
6
+ module RailsForge
7
+ # RefactorController class extracts services from fat controller methods
8
+ class RefactorController
9
+ # Minimum lines to consider a method for extraction
10
+ MIN_METHOD_LINES = 15
11
+
12
+ # Initialize the refactor
13
+ def initialize(base_path = nil)
14
+ @base_path = base_path || find_rails_app_path
15
+ raise RefactorControllerError, "Not in a Rails application directory" unless @base_path
16
+ end
17
+
18
+ # Refactor a specific controller
19
+ # @param controller_name [String] Name of controller to refactor
20
+ # @return [Hash] Refactoring results
21
+ def refactor(controller_name)
22
+ controller_file = find_controller_file(controller_name)
23
+
24
+ unless controller_file
25
+ raise RefactorControllerError, "Controller '#{controller_name}' not found"
26
+ end
27
+
28
+ puts "Analyzing controller: #{controller_name}"
29
+ puts ""
30
+
31
+ # Read controller content
32
+ content = File.read(controller_file)
33
+
34
+ # Find methods that can be extracted
35
+ long_methods = find_long_methods(content)
36
+
37
+ if long_methods.empty?
38
+ puts "No methods found that need extraction (min #{MIN_METHOD_LINES} lines)"
39
+ return { extracted: [], controller: controller_name }
40
+ end
41
+
42
+ puts "Found #{long_methods.count} method(s) to extract:"
43
+ long_methods.each do |method|
44
+ puts " - #{method[:name]} (#{method[:lines]} lines)"
45
+ end
46
+ puts ""
47
+
48
+ # Extract each method to a service
49
+ extracted = []
50
+ long_methods.each do |method|
51
+ service = extract_method_to_service(controller_name, method)
52
+ if service
53
+ update_controller_method(controller_file, method, service)
54
+ extracted << service
55
+ end
56
+ end
57
+
58
+ puts "Extraction complete!"
59
+ puts " Created #{extracted.count} service(s)"
60
+ puts ""
61
+
62
+ { extracted: extracted, controller: controller_name, methods: long_methods }
63
+ end
64
+
65
+ # Preview what would be extracted without making changes
66
+ # @param controller_name [String] Name of controller to analyze
67
+ # @return [Array] Methods that would be extracted
68
+ def preview(controller_name)
69
+ controller_file = find_controller_file(controller_name)
70
+
71
+ unless controller_file
72
+ raise RefactorControllerError, "Controller '#{controller_name}' not found"
73
+ end
74
+
75
+ content = File.read(controller_file)
76
+ find_long_methods(content)
77
+ end
78
+
79
+ private
80
+
81
+ # Find the controller file path
82
+ def find_rails_app_path
83
+ path = Dir.pwd
84
+ max_depth = 10
85
+
86
+ max_depth.times do
87
+ return path if File.exist?(File.join(path, "config", "application.rb"))
88
+ parent = File.dirname(path)
89
+ break if parent == path
90
+ path = parent
91
+ end
92
+
93
+ nil
94
+ end
95
+
96
+ # Find controller file by name
97
+ def find_controller_file(controller_name)
98
+ controllers_dir = File.join(@base_path, "app", "controllers")
99
+
100
+ # Try different variations
101
+ [
102
+ "#{controller_name.underscore}_controller.rb",
103
+ "#{controller_name.underscore}_controller.rb"
104
+ ].each do |filename|
105
+ path = File.join(controllers_dir, filename)
106
+ return path if File.exist?(path)
107
+ end
108
+
109
+ # Search recursively
110
+ Dir.glob(File.join(controllers_dir, "**", "*_controller.rb")).each do |file|
111
+ return file if File.basename(file).include?(controller_name.underscore)
112
+ end
113
+
114
+ nil
115
+ end
116
+
117
+ # Find methods that exceed the line threshold
118
+ def find_long_methods(content)
119
+ methods = []
120
+ lines = content.lines
121
+
122
+ # Simple regex to find method definitions
123
+ in_method = false
124
+ method_start = 0
125
+ method_name = nil
126
+ method_lines = []
127
+
128
+ lines.each_with_index do |line, index|
129
+ # Match def method_name or def self.method_name
130
+ if line =~ /\bdef\s+(self\.)?(\w+)/
131
+ if in_method
132
+ # Close previous method
133
+ methods << {
134
+ name: method_name,
135
+ start: method_start,
136
+ lines: method_lines.count,
137
+ content: method_lines.join
138
+ }
139
+ end
140
+
141
+ in_method = true
142
+ method_start = index
143
+ method_name = $2
144
+ method_lines = [line]
145
+ elsif in_method
146
+ if line.strip.start_with?("end") && method_lines.last =~ /\b(end|else|elsif|when)\b/
147
+ # This might be the end of the method
148
+ method_lines << line
149
+
150
+ # Check if this is the closing end
151
+ depth = method_lines.join.scan(/\b(begin|class|module|def|do|if|case)\b/).count
152
+ end_depth = method_lines.join.scan(/\bend\b/).count
153
+
154
+ if depth == end_depth && depth > 0
155
+ methods << {
156
+ name: method_name,
157
+ start: method_start,
158
+ lines: method_lines.count,
159
+ content: method_lines.join
160
+ }
161
+ in_method = false
162
+ method_lines = []
163
+ end
164
+ else
165
+ method_lines << line
166
+ end
167
+ end
168
+ end
169
+
170
+ # Close last method if still open
171
+ if in_method && method_lines.any?
172
+ methods << {
173
+ name: method_name,
174
+ start: method_start,
175
+ lines: method_lines.count,
176
+ content: method_lines.join
177
+ }
178
+ end
179
+
180
+ # Filter to only long methods
181
+ methods.select { |m| m[:lines] >= MIN_METHOD_LINES }
182
+ end
183
+
184
+ # Extract a method to a service object
185
+ def extract_method_to_service(controller_name, method_info)
186
+ service_dir = File.join(@base_path, "app", "services", controller_name.underscore)
187
+ FileUtils.mkdir_p(service_dir)
188
+
189
+ service_name = "#{method_info[:name]}_service"
190
+ service_class_name = "#{controller_name}#{method_info[:name].camelize}Service"
191
+
192
+ service_path = File.join(service_dir, "#{service_name}.rb")
193
+
194
+ if File.exist?(service_path)
195
+ puts " Skipping #{service_name} (already exists)"
196
+ return nil
197
+ end
198
+
199
+ # Generate service content
200
+ service_content = generate_service_content(service_class_name, method_info)
201
+
202
+ File.write(service_path, service_content)
203
+ puts " Created app/services/#{controller_name.underscore}/#{service_name}.rb"
204
+
205
+ { name: service_name, class: service_class_name, path: service_path }
206
+ end
207
+
208
+ # Generate service object content
209
+ def generate_service_content(class_name, method_info)
210
+ <<~RUBY
211
+ # Service extracted from controller method: #{method_info[:name]}
212
+ # Original method had #{method_info[:lines]} lines
213
+ class #{class_name}
214
+ def initialize(params = {})
215
+ @params = params
216
+ end
217
+
218
+ def call
219
+ # TODO: Refactor extracted logic here
220
+ # Original method content:
221
+ # #{method_info[:content].lines.first(5).join('# ')}
222
+ end
223
+
224
+ def self.call(params = {})
225
+ new(params).call
226
+ end
227
+ end
228
+ RUBY
229
+ end
230
+
231
+ # Update the controller method to use the service
232
+ def update_controller_method(controller_file, method_info, service)
233
+ content = File.read(controller_file)
234
+
235
+ # Replace method body with service call
236
+ new_method = <<~RUBY
237
+ def #{method_info[:name]}
238
+ result = #{service[:class]}.call(params)
239
+ end
240
+ RUBY
241
+
242
+ # Simple replacement - in production would use proper parsing
243
+ lines = content.lines
244
+
245
+ # Find and replace the method
246
+ modified_lines = []
247
+ in_target_method = false
248
+ method_depth = 0
249
+
250
+ lines.each_with_index do |line, index|
251
+ if line =~ /\bdef\s+(self\.)?#{method_info[:name]}\b/
252
+ in_target_method = true
253
+ modified_lines << new_method
254
+ next
255
+ end
256
+
257
+ if in_target_method
258
+ method_depth += 1 if line =~ /\b(def|class|module|if|case|do)\b/
259
+ method_depth -= 1 if line =~ /\bend\b/ && method_depth > 0
260
+
261
+ if method_depth == 0
262
+ in_target_method = false
263
+ end
264
+ next # Skip original method lines
265
+ end
266
+
267
+ modified_lines << line
268
+ end
269
+
270
+ File.write(controller_file, modified_lines.join)
271
+ puts " Updated controller method to use service"
272
+ end
273
+
274
+ # Error class
275
+ class RefactorControllerError < StandardError; end
276
+ end
277
+ end
@@ -0,0 +1,117 @@
1
+ # RefactorController module for RailsForge
2
+ # Automatically refactors fat controllers by extracting service objects
3
+
4
+ require 'fileutils'
5
+
6
+ module RailsForge
7
+ module Refactors
8
+ # RefactorController class extracts services from fat controller methods
9
+ class RefactorController
10
+ MIN_METHOD_LINES = 15
11
+
12
+ def initialize(base_path = nil)
13
+ @base_path = base_path || find_rails_app_path
14
+ raise RefactorControllerError, "Not in a Rails application" unless @base_path
15
+ end
16
+
17
+ # Refactor a specific controller
18
+ def refactor(controller_name)
19
+ controller_file = find_controller_file(controller_name)
20
+ raise RefactorControllerError, "Controller not found" unless controller_file
21
+
22
+ content = File.read(controller_file)
23
+ long_methods = find_long_methods(content)
24
+
25
+ if long_methods.empty?
26
+ puts "No methods to extract"
27
+ return { extracted: [] }
28
+ end
29
+
30
+ puts "Found #{long_methods.count} method(s) to extract"
31
+
32
+ extracted = []
33
+ long_methods.each do |method|
34
+ service = extract_to_service(controller_name, method)
35
+ extracted << service if service
36
+ end
37
+
38
+ { extracted: extracted }
39
+ end
40
+
41
+ private
42
+
43
+ def find_rails_app_path
44
+ path = Dir.pwd
45
+ 10.times do
46
+ return path if File.exist?(File.join(path, "config", "application.rb"))
47
+ parent = File.dirname(path)
48
+ break if parent == path
49
+ path = parent
50
+ end
51
+ nil
52
+ end
53
+
54
+ def find_controller_file(name)
55
+ dir = File.join(@base_path, "app", "controllers")
56
+ return nil unless Dir.exist?(dir)
57
+
58
+ Dir.glob(File.join(dir, "**", "*_controller.rb")).find do |f|
59
+ File.basename(f).include?(name.underscore)
60
+ end
61
+ end
62
+
63
+ def find_long_methods(content)
64
+ methods = []
65
+ lines = content.lines
66
+ in_method = false
67
+ method_lines = []
68
+ method_name = nil
69
+
70
+ lines.each_with_index do |line, i|
71
+ if line =~ /\bdef\s+(\w+)/
72
+ methods << { name: method_name, lines: method_lines.count } if in_method
73
+ in_method = true
74
+ method_name = $1
75
+ method_lines = [line]
76
+ elsif in_method
77
+ method_lines << line
78
+ if line.strip == "end" && method_lines.count > 1
79
+ methods << { name: method_name, lines: method_lines.count, content: method_lines.join }
80
+ in_method = false
81
+ end
82
+ end
83
+ end
84
+
85
+ methods.select { |m| m[:lines] >= MIN_METHOD_LINES }
86
+ end
87
+
88
+ def extract_to_service(controller_name, method_info)
89
+ service_dir = File.join(@base_path, "app", "services", controller_name.underscore)
90
+ FileUtils.mkdir_p(service_dir)
91
+
92
+ service_name = "#{method_info[:name]}_service"
93
+ service_path = File.join(service_dir, "#{service_name}.rb")
94
+
95
+ return nil if File.exist?(service_path)
96
+
97
+ File.write(service_path, <<~RUBY)
98
+ # Extracted from controller: #{controller_name}
99
+ class #{controller_name}#{method_info[:name].capitalize}Service
100
+ def initialize(params = {})
101
+ @params = params
102
+ end
103
+
104
+ def call
105
+ # TODO: Implement extracted logic
106
+ end
107
+ end
108
+ RUBY
109
+
110
+ puts " Created app/services/#{controller_name.underscore}/#{service_name}.rb"
111
+ { name: service_name }
112
+ end
113
+
114
+ class RefactorControllerError < StandardError; end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,105 @@
1
+ # TemplateLoader module handles loading template versions
2
+ require 'fileutils'
3
+
4
+ module RailsForge
5
+ # Template module for managing generator templates
6
+ module Template
7
+ # Error class for template loading issues
8
+ class TemplateError < StandardError; end
9
+
10
+ # Template directory path
11
+ TEMPLATES_DIR = File.expand_path('../templates', __FILE__)
12
+
13
+ # Default template version
14
+ DEFAULT_VERSION = "v1"
15
+
16
+ # Get available template versions
17
+ # @return [Array<String>] Array of version names
18
+ def self.available_versions
19
+ return [] unless Dir.exist?(TEMPLATES_DIR)
20
+
21
+ Dir.glob(File.join(TEMPLATES_DIR, 'v*')).map do |dir|
22
+ File.basename(dir)
23
+ end.sort
24
+ end
25
+
26
+ # Load template for a specific generator and version
27
+ # @param generator_type [String] Type of generator (service, query, etc.)
28
+ # @param version [String] Template version (e.g., "v1")
29
+ # @return [Hash] Template configuration
30
+ def self.load_template(generator_type, version = DEFAULT_VERSION)
31
+ template_path = File.join(TEMPLATES_DIR, version, generator_type, "template.rb")
32
+
33
+ unless File.exist?(template_path)
34
+ raise TemplateError, "Template not found for #{generator_type} version #{version}"
35
+ end
36
+
37
+ {
38
+ template_path: template_path,
39
+ version: version,
40
+ generator_type: generator_type
41
+ }
42
+ end
43
+
44
+ # Get template content
45
+ # @param generator_type [String] Type of generator
46
+ # @param version [String] Template version
47
+ # @return [String] Template content
48
+ def self.get_content(generator_type, version = DEFAULT_VERSION)
49
+ template_info = load_template(generator_type, version)
50
+ File.read(template_info[:template_path])
51
+ end
52
+
53
+ # Check if template exists
54
+ # @param generator_type [String] Type of generator
55
+ # @param version [String] Template version
56
+ # @return [Boolean] True if template exists
57
+ def self.exists?(generator_type, version = DEFAULT_VERSION)
58
+ template_path = File.join(TEMPLATES_DIR, version, generator_type, "template.rb")
59
+ File.exist?(template_path)
60
+ end
61
+
62
+ # List templates in a version
63
+ # @param version [String] Template version
64
+ # @return [Array<String>] List of generator types
65
+ def self.list_templates(version = DEFAULT_VERSION)
66
+ version_path = File.join(TEMPLATES_DIR, version)
67
+ return [] unless Dir.exist?(version_path)
68
+
69
+ Dir.glob(File.join(version_path, '*')).select do |dir|
70
+ File.directory?(dir)
71
+ end.map do |dir|
72
+ File.basename(dir)
73
+ end
74
+ end
75
+
76
+ # Show available versions with their templates
77
+ # @return [String] Formatted list
78
+ def self.show_available
79
+ output = "Available template versions:\n\n"
80
+
81
+ available_versions.each do |version|
82
+ output += " #{version}:\n"
83
+ templates = list_templates(version)
84
+ templates.each do |template|
85
+ output += " - #{template}\n"
86
+ end
87
+ output += "\n"
88
+ end
89
+
90
+ output
91
+ end
92
+
93
+ # Get spec template content
94
+ # @param generator_type [String] Type of generator
95
+ # @param version [String] Template version
96
+ # @return [String] Spec template content
97
+ def self.get_spec_content(generator_type, version = DEFAULT_VERSION)
98
+ spec_path = File.join(TEMPLATES_DIR, version, generator_type, "spec_template.rb")
99
+
100
+ return nil unless File.exist?(spec_path)
101
+
102
+ File.read(spec_path)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,18 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= name.camelize %>Form do
4
+ let(:params) { {} }
5
+ subject { described_class.new(<%= name.underscore %>_params: params) }
6
+
7
+ describe '#valid?' do
8
+ it 'is valid with valid params' do
9
+ expect(subject.valid?).to be_truthy
10
+ end
11
+ end
12
+
13
+ describe '#save' do
14
+ it 'saves successfully' do
15
+ expect(subject.save).to be_truthy
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ # Form class for <%= name %>
2
+ # Encapsulates form validation logic
3
+ #
4
+ # Usage:
5
+ # form = <%= name.camelize %>Form.new(params)
6
+ # if form.valid?
7
+ # form.save
8
+ # end
9
+ class <%= name.camelize %>Form
10
+ include ActiveModel::Model
11
+
12
+ attr_accessor <%= name.underscore %>_attributes
13
+
14
+ validate :validate_<%= name.underscore %>
15
+
16
+ def save
17
+ return false unless valid?
18
+
19
+ # TODO: Implement save logic
20
+ true
21
+ end
22
+
23
+ private
24
+
25
+ def validate_<%= name.underscore %>
26
+ # TODO: Add validations
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= name.camelize %>Job do
4
+ let(:args) { [] }
5
+
6
+ it 'enqueues the job' do
7
+ expect {
8
+ described_class.perform_later(*args)
9
+ }.to have_enqueued_job(described_class)
10
+ end
11
+
12
+ describe '#perform' do
13
+ it 'performs the job' do
14
+ expect { subject.perform(*args) }.not_to raise_error
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # Background job for <%= name %>
2
+ # Handles async processing
3
+ #
4
+ # Usage:
5
+ # <%= name.camelize %>Job.perform_later(record)
6
+ # <%= name.camelize %>Job.perform_now(record)
7
+ class <%= name.camelize %>Job < ApplicationJob
8
+ queue_as :default
9
+
10
+ def perform(*args)
11
+ # TODO: Implement job logic
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= name.camelize %>Policy do
4
+ let(:user) { nil }
5
+ let(:record) { <%= name.camelize %>.new }
6
+ subject { described_class.new(user, record) }
7
+
8
+ describe '#index?' do
9
+ it 'allows everyone' do
10
+ expect(subject.index?).to be_truthy
11
+ end
12
+ end
13
+
14
+ describe '#show?' do
15
+ it 'allows everyone' do
16
+ expect(subject.show?).to be_truthy
17
+ end
18
+ end
19
+
20
+ describe '#create?' do
21
+ it 'requires user' do
22
+ expect(subject.create?).to be_falsey
23
+ end
24
+
25
+ it 'allows authenticated user' do
26
+ user = User.new
27
+ expect(described_class.new(user, record).create?).to be_truthy
28
+ end
29
+ end
30
+
31
+ describe 'Scope' do
32
+ let(:scope) { <%= name.camelize %>.all }
33
+ subject { described_class::Scope.new(user, scope) }
34
+
35
+ describe '#resolve' do
36
+ it 'returns all records' do
37
+ expect(subject.resolve).to eq(scope.all)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,57 @@
1
+ # Policy class for <%= name %>
2
+ # Handles authorization logic
3
+ #
4
+ # Usage:
5
+ # authorize <%= name.underscore %>
6
+ # policy_scope(<%= name.underscore %>)
7
+ class <%= name.camelize %>Policy
8
+ attr_reader :user, :record
9
+
10
+ def initialize(user, record)
11
+ @user = user
12
+ @record = record
13
+ end
14
+
15
+ def index?
16
+ true
17
+ end
18
+
19
+ def show?
20
+ true
21
+ end
22
+
23
+ def create?
24
+ user.present?
25
+ end
26
+
27
+ def new?
28
+ create?
29
+ end
30
+
31
+ def update?
32
+ user.present?
33
+ end
34
+
35
+ def edit?
36
+ update?
37
+ end
38
+
39
+ def destroy?
40
+ user.present?
41
+ end
42
+
43
+ class Scope
44
+ def initialize(user, scope)
45
+ @user = user
46
+ @scope = scope
47
+ end
48
+
49
+ def resolve
50
+ scope.all
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :user, :scope
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= name.camelize %>Presenter do
4
+ let(:<%= name.underscore %>) { <%= name.camelize %>.new }
5
+ subject { described_class.new(<%= name.underscore %>) }
6
+
7
+ describe 'initialization' do
8
+ it 'initializes successfully' do
9
+ expect(subject).to be_a(described_class)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # Presenter class for <%= name %>
2
+ # Handles view presentation logic
3
+ #
4
+ # Usage:
5
+ # presenter = <%= name.camelize %>Presenter.new(@<%= name.underscore %>)
6
+ # presenter.full_name
7
+ class <%= name.camelize %>Presenter
8
+ def initialize(<%= name.underscore %>)
9
+ @<%= name.underscore %> = <%= name.underscore %>
10
+ end
11
+
12
+ # TODO: Add presenter methods
13
+ end