rails_code_health 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1fcf270392e52c8b6ab9783a9ed2ffaa133825210ff18c0c4ec124ce8b8d2b7f
4
+ data.tar.gz: 6ddc72d1451b604e7df94e655e22b225647842dcbd0a00baec0618d4e3b1c823
5
+ SHA512:
6
+ metadata.gz: 416a30e4fd42da4f2f8fe100e349c611c009d5468710974c59d945036145ce1c0df543ca1a8f51f3bf53868cd6ce9472e4be3457dcdaca3d5de12258ef16ed77
7
+ data.tar.gz: 44e261b45a963e81c5d3ace4be6b360ff8c052ba39dd4b0e3fa56789c145673e3b51d5359ebed08a2f7eb50214044031f0f6185760e3ac06072531be25659769
data/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2025-06-17
11
+
12
+ ### Added
13
+ - Initial release of Rails Code Health analyzer
14
+ - Ruby code complexity analysis (cyclomatic complexity, method length, class length)
15
+ - Rails-specific pattern detection for controllers, models, views, helpers, and migrations
16
+ - Health scoring system (1-10 scale) based on CodeScene research
17
+ - Command-line interface with console and JSON output options
18
+ - Configurable thresholds and scoring weights
19
+ - Actionable recommendations for code improvements
20
+ - Support for Ruby 3.0+ and Rails 7.0+
21
+
22
+ ### Features
23
+ - **Ruby Analysis**: Method/class length, cyclomatic complexity, nesting depth, parameter count
24
+ - **Rails Analysis**: Controller actions, model validations, view logic detection, migration complexity
25
+ - **Code Smells**: God classes/methods, long parameter lists, nested conditionals, missing validations
26
+ - **Reporting**: Detailed console output with health categories and JSON export
27
+ - **CLI**: `rails-health` command with options for format, output file, and custom configuration
28
+
29
+ [Unreleased]: https://github.com/yourusername/rails_code_health/compare/v0.1.0...HEAD
30
+ [0.1.0]: https://github.com/yourusername/rails_code_health/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 George Kosmopoulos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # Rails Code Health
2
+
3
+ A Ruby gem that evaluates the code health of Ruby on Rails applications, inspired by CodeScene's research on technical debt and maintainability.
4
+
5
+ ## Overview
6
+
7
+ Rails Code Health analyzes your Rails codebase and provides:
8
+ - **Health scores** (1-10 scale) for each file based on complexity, maintainability, and Rails conventions
9
+ - **Categorization** of files into Healthy (🟢), Warning (🟡), Alert (🔴), and Critical (⚫) categories
10
+ - **Actionable recommendations** for improving code quality
11
+ - **Rails-specific analysis** for controllers, models, views, helpers, and migrations
12
+
13
+ ## Installation
14
+
15
+ Add this gem to your Rails application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'rails_code_health', group: :development
19
+ ```
20
+
21
+ Or install it globally:
22
+
23
+ ```bash
24
+ gem install rails_code_health
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Command Line Interface
30
+
31
+ Run analysis on your current Rails project:
32
+
33
+ ```bash
34
+ rails-health
35
+ ```
36
+
37
+ Analyze a specific Rails project:
38
+
39
+ ```bash
40
+ rails-health /path/to/rails/project
41
+ ```
42
+
43
+ Generate JSON output:
44
+
45
+ ```bash
46
+ rails-health --format json --output report.json
47
+ ```
48
+
49
+ Use custom configuration:
50
+
51
+ ```bash
52
+ rails-health --config custom_thresholds.json
53
+ ```
54
+
55
+ ### Programmatic Usage
56
+
57
+ ```ruby
58
+ require 'rails_code_health'
59
+
60
+ # Analyze current directory
61
+ report = RailsCodeHealth.analyze('.')
62
+
63
+ # Analyze specific path
64
+ report = RailsCodeHealth.analyze('/path/to/rails/project')
65
+
66
+ # Configure custom thresholds
67
+ RailsCodeHealth.configure do |config|
68
+ config.load_thresholds_from_file('custom_config.json')
69
+ end
70
+ ```
71
+
72
+ ## What It Analyzes
73
+
74
+ ### Ruby Code Metrics
75
+ - **Method length** - Long methods are harder to understand and maintain
76
+ - **Class length** - Large classes often violate single responsibility principle
77
+ - **Cyclomatic complexity** - High complexity increases defect risk
78
+ - **Nesting depth** - Deep nesting reduces readability
79
+ - **Parameter count** - Too many parameters suggest poor design
80
+ - **ABC complexity** - Assignments, branches, and conditions complexity
81
+
82
+ ### Rails-Specific Analysis
83
+
84
+ #### Controllers
85
+ - Action count per controller
86
+ - Strong parameters usage
87
+ - Direct model access detection
88
+ - Response format analysis
89
+
90
+ #### Models
91
+ - Association count
92
+ - Validation presence
93
+ - Callback complexity
94
+ - Fat model detection
95
+
96
+ #### Views
97
+ - Logic in views detection
98
+ - Inline styles/JavaScript
99
+ - Template length analysis
100
+
101
+ #### Helpers & Migrations
102
+ - Helper method count
103
+ - Migration complexity
104
+ - Data changes in migrations
105
+
106
+ ### Code Smells Detection
107
+ - **God Class/Method** - Classes or methods doing too much
108
+ - **Long Parameter List** - Methods with too many parameters
109
+ - **Nested Conditionals** - Deep if/else nesting
110
+ - **Missing Validations** - Models without proper validation
111
+ - **Logic in Views** - Business logic in presentation layer
112
+
113
+ ## Health Score Calculation
114
+
115
+ Health scores range from 1.0 (critical) to 10.0 (excellent) based on:
116
+
117
+ - **File type multipliers** - Different standards for different file types
118
+ - **Weighted penalties** - More important issues have higher impact
119
+ - **Rails conventions** - Adherence to Rails best practices
120
+ - **Code complexity** - Multiple complexity metrics combined
121
+
122
+ ### Score Categories
123
+
124
+ - **🟢 Healthy (8.0-10.0)**: Well-structured, maintainable code
125
+ - **🟡 Warning (4.0-7.9)**: Some issues, but generally acceptable
126
+ - **🔴 Alert (1.0-3.9)**: Significant problems requiring attention
127
+ - **⚫ Critical (<1.0)**: Severe issues, immediate action needed
128
+
129
+ ## Configuration
130
+
131
+ Create a custom `thresholds.json` file:
132
+
133
+ ```json
134
+ {
135
+ "ruby_thresholds": {
136
+ "method_length": {
137
+ "green": 15,
138
+ "yellow": 25,
139
+ "red": 40
140
+ },
141
+ "cyclomatic_complexity": {
142
+ "green": 6,
143
+ "yellow": 10,
144
+ "red": 15
145
+ }
146
+ },
147
+ "rails_specific": {
148
+ "controller_actions": {
149
+ "green": 5,
150
+ "yellow": 10,
151
+ "red": 20
152
+ }
153
+ },
154
+ "scoring_weights": {
155
+ "method_length": 0.15,
156
+ "cyclomatic_complexity": 0.20,
157
+ "rails_conventions": 0.15
158
+ }
159
+ }
160
+ ```
161
+
162
+ ## Sample Output
163
+
164
+ ```
165
+ Rails Code Health Report
166
+ ==================================================
167
+
168
+ 📊 Overall Health Summary:
169
+ Total files analyzed: 45
170
+ 🟢 Healthy files (8.0-10.0): 32 (71.1%)
171
+ 🟡 Warning files (4.0-7.9): 10 (22.2%)
172
+ 🔴 Alert files (1.0-3.9): 3 (6.7%)
173
+
174
+ 📈 Average Health Score: 7.8/10.0
175
+
176
+ 📂 Breakdown by File Type:
177
+ Controller: 8 files, avg score: 7.2, 5 healthy
178
+ Model: 12 files, avg score: 8.4, 10 healthy
179
+ View: 15 files, avg score: 8.1, 12 healthy
180
+
181
+ 🚨 Files Needing Most Attention:
182
+ 1. 🔴 app/controllers/admin/reports_controller.rb
183
+ Score: 3.2/10.0 | Type: controller | Size: 15.2 KB
184
+ Top issues:
185
+ • Break down the generate_report method (156 lines) into smaller methods
186
+ • Consider splitting this controller - it has 18 actions
187
+
188
+ 💡 Key Recommendations:
189
+ 1. Reduce method and class lengths
190
+ 2. Lower cyclomatic complexity
191
+ 3. Follow Rails conventions
192
+ 4. Extract business logic from controllers
193
+ ```
194
+
195
+ ## Research Foundation
196
+
197
+ This gem is inspired by the peer-reviewed research paper ["Code Red: The Business Impact of Code Quality"](https://arxiv.org/pdf/2203.04374) by Adam Tornhill and Markus Borg, which found that:
198
+
199
+ - Low quality code contains **15x more defects** than high quality code
200
+ - Resolving issues in low quality code takes **124% more time**
201
+ - Issue resolutions involve **9x longer maximum cycle times**
202
+
203
+ ## Development
204
+
205
+ After checking out the repo, run:
206
+
207
+ ```bash
208
+ bundle install
209
+ ```
210
+
211
+ Run tests:
212
+
213
+ ```bash
214
+ bundle exec rspec
215
+ ```
216
+
217
+ Run the gem locally:
218
+
219
+ ```bash
220
+ bundle exec bin/rails-health /path/to/rails/project
221
+ ```
222
+
223
+ ## Contributing
224
+
225
+ 1. Fork it
226
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
227
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
228
+ 4. Push to the branch (`git push origin my-new-feature`)
229
+ 5. Create a new Pull Request
230
+
231
+ ## License
232
+
233
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
234
+
235
+ ## Credits
236
+
237
+ Inspired by [CodeScene](https://codescene.com/) and the research on technical debt's business impact. This gem implements similar concepts specifically for Ruby on Rails applications.
data/bin/rails-health ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/rails_code_health'
4
+
5
+ RailsCodeHealth::CLI.start(ARGV)
@@ -0,0 +1,80 @@
1
+ {
2
+ "ruby_thresholds": {
3
+ "method_length": {
4
+ "green": 15,
5
+ "yellow": 25,
6
+ "red": 40
7
+ },
8
+ "class_length": {
9
+ "green": 100,
10
+ "yellow": 200,
11
+ "red": 400
12
+ },
13
+ "cyclomatic_complexity": {
14
+ "green": 6,
15
+ "yellow": 10,
16
+ "red": 15
17
+ },
18
+ "abc_complexity": {
19
+ "green": 15,
20
+ "yellow": 25,
21
+ "red": 40
22
+ },
23
+ "nesting_depth": {
24
+ "green": 3,
25
+ "yellow": 5,
26
+ "red": 8
27
+ },
28
+ "parameter_count": {
29
+ "green": 3,
30
+ "yellow": 5,
31
+ "red": 8
32
+ }
33
+ },
34
+ "rails_specific": {
35
+ "controller_actions": {
36
+ "green": 5,
37
+ "yellow": 10,
38
+ "red": 20
39
+ },
40
+ "controller_length": {
41
+ "green": 50,
42
+ "yellow": 100,
43
+ "red": 200
44
+ },
45
+ "model_public_methods": {
46
+ "green": 7,
47
+ "yellow": 12,
48
+ "red": 20
49
+ },
50
+ "view_length": {
51
+ "green": 30,
52
+ "yellow": 50,
53
+ "red": 100
54
+ },
55
+ "migration_complexity": {
56
+ "green": 10,
57
+ "yellow": 20,
58
+ "red": 40
59
+ }
60
+ },
61
+ "file_type_multipliers": {
62
+ "controllers": 1.2,
63
+ "models": 1.0,
64
+ "views": 0.8,
65
+ "helpers": 0.9,
66
+ "lib": 1.1,
67
+ "specs": 0.7,
68
+ "migrations": 0.6
69
+ },
70
+ "scoring_weights": {
71
+ "method_length": 0.15,
72
+ "class_length": 0.12,
73
+ "cyclomatic_complexity": 0.20,
74
+ "nesting_depth": 0.18,
75
+ "parameter_count": 0.10,
76
+ "duplication": 0.25,
77
+ "rails_conventions": 0.15,
78
+ "code_smells": 0.25
79
+ }
80
+ }
@@ -0,0 +1,164 @@
1
+ require 'optparse'
2
+
3
+ module RailsCodeHealth
4
+ class CLI
5
+ def self.start(args)
6
+ new(args).run
7
+ end
8
+
9
+ def initialize(args)
10
+ @args = args
11
+ @options = {
12
+ path: '.',
13
+ format: :console,
14
+ output: nil,
15
+ config: nil,
16
+ verbose: false
17
+ }
18
+ end
19
+
20
+ def run
21
+ parse_options
22
+
23
+ begin
24
+ # Load custom config if provided
25
+ if @options[:config]
26
+ RailsCodeHealth.configuration.load_thresholds_from_file(@options[:config])
27
+ end
28
+
29
+ # Set output format
30
+ RailsCodeHealth.configuration.output_format = @options[:format]
31
+
32
+ puts "🔍 Analyzing Rails project at: #{@options[:path]}" if @options[:verbose]
33
+ puts "📊 Using format: #{@options[:format]}" if @options[:verbose]
34
+
35
+ # Run the analysis
36
+ report = analyze_project
37
+
38
+ # Output the report
39
+ output_report(report)
40
+
41
+ puts "\n✅ Analysis complete!" if @options[:verbose]
42
+
43
+ rescue RailsCodeHealth::Error => e
44
+ puts "❌ Error: #{e.message}"
45
+ exit 1
46
+ rescue => e
47
+ puts "💥 Unexpected error: #{e.message}"
48
+ puts e.backtrace.join("\n") if @options[:verbose]
49
+ exit 1
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def parse_options
56
+ OptionParser.new do |opts|
57
+ opts.banner = "Usage: rails-health [options] [path]"
58
+ opts.separator ""
59
+ opts.separator "Analyze the code health of a Ruby on Rails application"
60
+ opts.separator ""
61
+ opts.separator "Options:"
62
+
63
+ opts.on("-f", "--format FORMAT", [:console, :json],
64
+ "Output format (console, json)") do |format|
65
+ @options[:format] = format
66
+ end
67
+
68
+ opts.on("-o", "--output FILE",
69
+ "Output file (default: stdout)") do |file|
70
+ @options[:output] = file
71
+ end
72
+
73
+ opts.on("-c", "--config FILE",
74
+ "Custom configuration file") do |file|
75
+ @options[:config] = file
76
+ end
77
+
78
+ opts.on("-v", "--verbose",
79
+ "Verbose output") do
80
+ @options[:verbose] = true
81
+ end
82
+
83
+ opts.on_tail("-h", "--help", "Show this message") do
84
+ puts opts
85
+ exit
86
+ end
87
+
88
+ opts.on_tail("--version", "Show version") do
89
+ puts "Rails Code Health v#{RailsCodeHealth::VERSION}"
90
+ exit
91
+ end
92
+ end.parse!(@args)
93
+
94
+ # Use remaining argument as path if provided
95
+ @options[:path] = @args.first if @args.any?
96
+ end
97
+
98
+ def analyze_project
99
+ project_path = Pathname.new(@options[:path]).expand_path
100
+
101
+ unless ProjectDetector.rails_project?(project_path)
102
+ raise Error, "Not a Rails project directory: #{project_path}"
103
+ end
104
+
105
+ puts "📁 Found Rails project!" if @options[:verbose]
106
+
107
+ analyzer = FileAnalyzer.new(project_path)
108
+ results = analyzer.analyze_all
109
+
110
+ puts "📝 Analyzed #{results.count} files" if @options[:verbose]
111
+
112
+ health_calculator = HealthCalculator.new
113
+ scored_results = health_calculator.calculate_scores(results)
114
+
115
+ puts "🧮 Calculated health scores" if @options[:verbose]
116
+
117
+ scored_results
118
+ end
119
+
120
+ def output_report(results)
121
+ case @options[:format]
122
+ when :console
123
+ output_console_report(results)
124
+ when :json
125
+ output_json_report(results)
126
+ end
127
+ end
128
+
129
+ def output_console_report(results)
130
+ report_generator = ReportGenerator.new(results)
131
+
132
+ if @options[:output]
133
+ File.write(@options[:output], capture_console_output(report_generator))
134
+ puts "📄 Report saved to: #{@options[:output]}"
135
+ else
136
+ report_generator.generate
137
+ end
138
+ end
139
+
140
+ def output_json_report(results)
141
+ report_generator = ReportGenerator.new(results)
142
+ json_report = report_generator.generate_json_report
143
+
144
+ if @options[:output]
145
+ File.write(@options[:output], json_report)
146
+ puts "📄 JSON report saved to: #{@options[:output]}"
147
+ else
148
+ puts json_report
149
+ end
150
+ end
151
+
152
+ def capture_console_output(report_generator)
153
+ original_stdout = $stdout
154
+ $stdout = StringIO.new
155
+
156
+ report_generator.generate
157
+
158
+ output = $stdout.string
159
+ $stdout = original_stdout
160
+
161
+ output
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,89 @@
1
+ module RailsCodeHealth
2
+ class Configuration
3
+ attr_accessor :thresholds, :excluded_paths, :output_format
4
+
5
+ def initialize
6
+ @thresholds = load_default_thresholds
7
+ @excluded_paths = default_excluded_paths
8
+ @output_format = :console
9
+ end
10
+
11
+ def thresholds
12
+ @thresholds ||= load_default_thresholds
13
+ end
14
+
15
+ def load_thresholds_from_file(file_path)
16
+ if File.exist?(file_path)
17
+ @thresholds = JSON.parse(File.read(file_path))
18
+ else
19
+ raise Error, "Thresholds file not found: #{file_path}"
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def load_default_thresholds
26
+ config_file = File.join(File.dirname(__FILE__), '..', '..', 'config', 'thresholds.json')
27
+ if File.exist?(config_file)
28
+ JSON.parse(File.read(config_file))
29
+ else
30
+ # Fallback to hardcoded defaults if config file is missing
31
+ default_hardcoded_thresholds
32
+ end
33
+ end
34
+
35
+ def default_excluded_paths
36
+ [
37
+ 'vendor/**/*',
38
+ 'tmp/**/*',
39
+ 'log/**/*',
40
+ 'node_modules/**/*',
41
+ 'coverage/**/*',
42
+ '.git/**/*',
43
+ 'public/assets/**/*',
44
+ 'db/schema.rb',
45
+ 'spec/**/*',
46
+ 'test/**/*'
47
+ ]
48
+ end
49
+
50
+ def default_hardcoded_thresholds
51
+ {
52
+ 'ruby_thresholds' => {
53
+ 'method_length' => { 'green' => 15, 'yellow' => 25, 'red' => 40 },
54
+ 'class_length' => { 'green' => 100, 'yellow' => 200, 'red' => 400 },
55
+ 'cyclomatic_complexity' => { 'green' => 6, 'yellow' => 10, 'red' => 15 },
56
+ 'abc_complexity' => { 'green' => 15, 'yellow' => 25, 'red' => 40 },
57
+ 'nesting_depth' => { 'green' => 3, 'yellow' => 5, 'red' => 8 },
58
+ 'parameter_count' => { 'green' => 3, 'yellow' => 5, 'red' => 8 }
59
+ },
60
+ 'rails_specific' => {
61
+ 'controller_actions' => { 'green' => 5, 'yellow' => 10, 'red' => 20 },
62
+ 'controller_length' => { 'green' => 50, 'yellow' => 100, 'red' => 200 },
63
+ 'model_public_methods' => { 'green' => 7, 'yellow' => 12, 'red' => 20 },
64
+ 'view_length' => { 'green' => 30, 'yellow' => 50, 'red' => 100 },
65
+ 'migration_complexity' => { 'green' => 10, 'yellow' => 20, 'red' => 40 }
66
+ },
67
+ 'file_type_multipliers' => {
68
+ 'controllers' => 1.2,
69
+ 'models' => 1.0,
70
+ 'views' => 0.8,
71
+ 'helpers' => 0.9,
72
+ 'lib' => 1.1,
73
+ 'specs' => 0.7,
74
+ 'migrations' => 0.6
75
+ },
76
+ 'scoring_weights' => {
77
+ 'method_length' => 0.15,
78
+ 'class_length' => 0.12,
79
+ 'cyclomatic_complexity' => 0.20,
80
+ 'nesting_depth' => 0.18,
81
+ 'parameter_count' => 0.10,
82
+ 'duplication' => 0.25,
83
+ 'rails_conventions' => 0.15,
84
+ 'code_smells' => 0.25
85
+ }
86
+ }
87
+ end
88
+ end
89
+ end