rails-architect 0.1.0 → 0.2.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 +4 -4
- data/README.md +309 -67
- data/config/rails_architect.yml +25 -0
- data/lib/rails_architect/dry_analyzer.rb +143 -0
- data/lib/rails_architect/kiss_analyzer.rb +130 -0
- data/lib/rails_architect/railtie.rb +11 -0
- data/lib/rails_architect/solid_analyzer.rb +196 -0
- data/lib/rails_architect/tasks/rails_architect.rake +35 -0
- data/lib/rails_architect/version.rb +5 -0
- data/lib/rails_architect.rb +30 -0
- data/test/rails_app/README.md +72 -0
- data/test/rails_app/app/controllers/posts_controller.rb +54 -0
- data/test/rails_app/app/controllers/users_controller.rb +104 -0
- data/test/rails_app/app/models/post.rb +34 -0
- data/test/rails_app/app/models/report_generator.rb +87 -0
- data/test/rails_app/app/models/user.rb +79 -0
- data/test/rails_app/config/rails_architect.yml +14 -0
- data/test/rails_app_integration_test.rb +50 -0
- data/test/rails_architect_test.rb +21 -0
- data/test/test_helper.rb +15 -0
- metadata +20 -7
- data/lib/rails/architect/version.rb +0 -7
- data/lib/rails/architect.rb +0 -10
- data/sig/rails/architect.rbs +0 -6
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
class ReportGenerator
|
|
2
|
+
# KISS Violation: Overly complex method with nested conditionals
|
|
3
|
+
# Method complexity exceeds threshold due to multiple control structures
|
|
4
|
+
def generate_complex_report
|
|
5
|
+
users = User.all
|
|
6
|
+
report_data = []
|
|
7
|
+
|
|
8
|
+
users.each do |user|
|
|
9
|
+
user_data = {}
|
|
10
|
+
|
|
11
|
+
# Deeply nested conditionals (KISS violation)
|
|
12
|
+
if user.active?
|
|
13
|
+
if user.admin?
|
|
14
|
+
user_data[:role] = 'Administrator'
|
|
15
|
+
user_data[:permissions] = ['read', 'write', 'delete', 'manage_users']
|
|
16
|
+
elsif user.moderator?
|
|
17
|
+
user_data[:role] = 'Moderator'
|
|
18
|
+
user_data[:permissions] = ['read', 'write', 'delete']
|
|
19
|
+
else
|
|
20
|
+
user_data[:role] = 'Member'
|
|
21
|
+
user_data[:permissions] = ['read', 'write']
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
user_data[:status] = 'Inactive'
|
|
25
|
+
user_data[:permissions] = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Complex case statement
|
|
29
|
+
case user.organization.size
|
|
30
|
+
when 1..10
|
|
31
|
+
user_data[:org_size] = 'Small'
|
|
32
|
+
when 11..50
|
|
33
|
+
user_data[:org_size] = 'Medium'
|
|
34
|
+
when 51..200
|
|
35
|
+
user_data[:org_size] = 'Large'
|
|
36
|
+
else
|
|
37
|
+
user_data[:org_size] = 'Enterprise'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Multiple loop types in one method (increases complexity)
|
|
41
|
+
while user.posts.any?
|
|
42
|
+
post = user.posts.first
|
|
43
|
+
if post.published?
|
|
44
|
+
user_data[:published_posts] ||= 0
|
|
45
|
+
user_data[:published_posts] += 1
|
|
46
|
+
end
|
|
47
|
+
break
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
until user.comments.empty?
|
|
51
|
+
comment = user.comments.first
|
|
52
|
+
if comment.approved?
|
|
53
|
+
user_data[:approved_comments] ||= 0
|
|
54
|
+
user_data[:approved_comments] += 1
|
|
55
|
+
end
|
|
56
|
+
break
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
for i in 0..user.posts.count
|
|
60
|
+
# Some complex logic here
|
|
61
|
+
break if i > 10
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
report_data << user_data
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
report_data
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# DRY Violation: Repeated calculation logic
|
|
71
|
+
def calculate_user_score_v1(user)
|
|
72
|
+
score = 0
|
|
73
|
+
score += user.posts.count * 10
|
|
74
|
+
score += user.comments.count * 5
|
|
75
|
+
score += user.likes.count * 2
|
|
76
|
+
score
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def calculate_user_score_v2(user)
|
|
80
|
+
score = 0
|
|
81
|
+
score += user.posts.count * 10
|
|
82
|
+
score += user.comments.count * 5
|
|
83
|
+
score += user.likes.count * 2
|
|
84
|
+
score
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
class User < ApplicationRecord
|
|
2
|
+
# Basic validations
|
|
3
|
+
validates :email, presence: true, uniqueness: true
|
|
4
|
+
validates :name, presence: true
|
|
5
|
+
|
|
6
|
+
# Associations
|
|
7
|
+
has_many :posts
|
|
8
|
+
has_many :comments
|
|
9
|
+
belongs_to :organization
|
|
10
|
+
|
|
11
|
+
# Scopes
|
|
12
|
+
scope :active, -> { where(active: true) }
|
|
13
|
+
scope :inactive, -> { where(active: false) }
|
|
14
|
+
|
|
15
|
+
# SOLID Violation: Too many methods (Fat Model - violates Single Responsibility)
|
|
16
|
+
# This model has 15+ instance methods doing different things
|
|
17
|
+
|
|
18
|
+
def full_name
|
|
19
|
+
"#{first_name} #{last_name}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def activate
|
|
23
|
+
update(active: true)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def deactivate
|
|
27
|
+
update(active: false)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def send_welcome_email
|
|
31
|
+
UserMailer.welcome(self).deliver_later
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_api_token
|
|
35
|
+
update(api_token: SecureRandom.hex(20))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def posts_count
|
|
39
|
+
posts.count
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def comments_count
|
|
43
|
+
comments.count
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def recent_posts(limit = 5)
|
|
47
|
+
posts.order(created_at: :desc).limit(limit)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def admin?
|
|
51
|
+
role == 'admin'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def moderator?
|
|
55
|
+
role == 'moderator'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def member?
|
|
59
|
+
role == 'member'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def promote_to_admin
|
|
63
|
+
update(role: 'admin')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def demote_to_member
|
|
67
|
+
update(role: 'member')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Additional methods to exceed threshold
|
|
71
|
+
def calculate_reputation
|
|
72
|
+
posts_count * 5 + comments_count * 2
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def send_notification(message)
|
|
76
|
+
# Notification logic
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
class RailsAppIntegrationTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@original_dir = Dir.pwd
|
|
8
|
+
@rails_app_path = File.join(__dir__, 'rails_app')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def teardown
|
|
12
|
+
Dir.chdir(@original_dir)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_analyzes_dummy_rails_app
|
|
16
|
+
Dir.chdir(@rails_app_path)
|
|
17
|
+
|
|
18
|
+
issues = RailsArchitect.analyze
|
|
19
|
+
|
|
20
|
+
# Should find several issues
|
|
21
|
+
assert issues.size > 0, "Expected to find architectural issues in dummy Rails app"
|
|
22
|
+
|
|
23
|
+
# Check for SOLID principle violations
|
|
24
|
+
solid_srp_issues = issues.select { |i| i[:type] == 'solid_srp' }
|
|
25
|
+
assert solid_srp_issues.size > 0, "Should detect SOLID SRP violations (fat models/controllers)"
|
|
26
|
+
|
|
27
|
+
# Check for KISS violations
|
|
28
|
+
kiss_complexity_issues = issues.select { |i| i[:type] == 'kiss_complexity' }
|
|
29
|
+
assert kiss_complexity_issues.size > 0, "Should detect KISS complexity violations"
|
|
30
|
+
|
|
31
|
+
# Check for DRY violations
|
|
32
|
+
dry_issues = issues.select { |i| i[:type].start_with?('dry_') }
|
|
33
|
+
assert dry_issues.size > 0, "Should detect DRY violations"
|
|
34
|
+
|
|
35
|
+
# Verify issue details for SOLID SRP
|
|
36
|
+
solid_srp_issues.each do |issue|
|
|
37
|
+
assert issue[:message].include?("Single Responsibility") || issue[:message].include?("extract to service objects"), "Expected SRP-related message, got: #{issue[:message]}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Verify issue details for KISS
|
|
41
|
+
kiss_complexity_issues.each do |issue|
|
|
42
|
+
assert_includes issue[:message], "complexity"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Verify issue details for DRY
|
|
46
|
+
dry_issues.each do |issue|
|
|
47
|
+
assert_includes issue[:message], "consider extracting"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
class RailsArchitectTest < Minitest::Test
|
|
6
|
+
def test_analyze_returns_array
|
|
7
|
+
# Mock the analyzers to avoid file system dependencies
|
|
8
|
+
mock_checker = Minitest::Mock.new
|
|
9
|
+
mock_checker.expect :check_models, nil
|
|
10
|
+
mock_checker.expect :check_controllers, nil
|
|
11
|
+
mock_checker.expect :issues, []
|
|
12
|
+
|
|
13
|
+
mock_complexity = Minitest::Mock.new
|
|
14
|
+
mock_complexity.expect :analyze, nil
|
|
15
|
+
mock_complexity.expect :issues, []
|
|
16
|
+
|
|
17
|
+
mock_perf_checker = Minitest::Mock.new
|
|
18
|
+
mock_perf_checker.expect :analyze, nil
|
|
19
|
+
mock_perf_checker.expect :issues, []
|
|
20
|
+
end
|
|
21
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-architect
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sami Dghim
|
|
@@ -121,9 +121,7 @@ dependencies:
|
|
|
121
121
|
- - "~>"
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
123
|
version: '5.0'
|
|
124
|
-
description: Rails Architect helps maintain clean Rails architecture
|
|
125
|
-
detecting and preventing common anti-patterns, monitoring code complexity, and providing
|
|
126
|
-
architectural visualizations.
|
|
124
|
+
description: Rails Architect helps maintain clean Rails architecture
|
|
127
125
|
email:
|
|
128
126
|
- samidghim@gmail.com
|
|
129
127
|
executables:
|
|
@@ -134,9 +132,24 @@ files:
|
|
|
134
132
|
- README.md
|
|
135
133
|
- Rakefile
|
|
136
134
|
- bin/rails-architect
|
|
137
|
-
-
|
|
138
|
-
- lib/
|
|
139
|
-
-
|
|
135
|
+
- config/rails_architect.yml
|
|
136
|
+
- lib/rails_architect.rb
|
|
137
|
+
- lib/rails_architect/dry_analyzer.rb
|
|
138
|
+
- lib/rails_architect/kiss_analyzer.rb
|
|
139
|
+
- lib/rails_architect/railtie.rb
|
|
140
|
+
- lib/rails_architect/solid_analyzer.rb
|
|
141
|
+
- lib/rails_architect/tasks/rails_architect.rake
|
|
142
|
+
- lib/rails_architect/version.rb
|
|
143
|
+
- test/rails_app/README.md
|
|
144
|
+
- test/rails_app/app/controllers/posts_controller.rb
|
|
145
|
+
- test/rails_app/app/controllers/users_controller.rb
|
|
146
|
+
- test/rails_app/app/models/post.rb
|
|
147
|
+
- test/rails_app/app/models/report_generator.rb
|
|
148
|
+
- test/rails_app/app/models/user.rb
|
|
149
|
+
- test/rails_app/config/rails_architect.yml
|
|
150
|
+
- test/rails_app_integration_test.rb
|
|
151
|
+
- test/rails_architect_test.rb
|
|
152
|
+
- test/test_helper.rb
|
|
140
153
|
homepage: https://github.com/samydghim/rails-architect
|
|
141
154
|
licenses: []
|
|
142
155
|
metadata:
|
data/lib/rails/architect.rb
DELETED